ERC-721은 블록체인 환경에서 고유한 가치를 가진 자산을 표현하기 위한 토큰화 표준입니다. 이 글에서는 ERC-721의 개념, 특징, 활용 사례에 대해 알아보겠습니다.
ERC721 표준
ERC721을 통하여 NFT를 발행하려면 contract에 아래의 함수가 반드시 제공되어야 합니다. 이러한 함수를 직접 작성하여 contract를 생성하여도 되고 interface를 이용하여 하여도 됩니다.
balanceOf(address _owner)
ownerOf(uint256 _tokenId)
safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data)
safeTransferFrom(address _from, address _to, uint256 _tokenId)
transferFrom(address _from, address _to, uint256 _tokenId)
approve(address _approved, uint256 _tokenId)
getApproved(uint256 _tokenId)
setApprovalForAll(address _operator, bool _approved)
isApprovedForAll(address _owner, address _operator)
위에서부터 각 함수의 역할을 설명하자면 아래와 같습니다.
balanceOf은 _owner가 가진 NFT의 개수를 반환
ownerOf는 _tokenId에 해당하는 NFT토큰의 소유자 address를 반환
safeTransferFrom는 아래의 safeTransferFrom을 실행하는 overloading 된 함수로 data 매개변수는 선택
safeTransferFrom는 _from이 가지고 있는 NFT를 _to에게 전송
transferFrom는 _from에서 _to로 _tokenId에 해당하는 NFT를 전송
approve는 _approved에게 _tokenId에 해당하는 NFT에 대한 권한을 부여
getApproved는 _tokenId에 해당하는 NFT에 대하여 현재 권한이 부여된 address반환
setApprovalForAll는 _operator주소에 대하여 현재 모든 주소의 NFT토큰에 대한 전송 승인 여부를 _approved 값으로 설정
isApprovedForAll는 _owner가 _operator에 대한 모든 NFT 전송 승인을 가지고 있는지 여부 반환
위의 함수를 interface로 작성을 한다면 아래와 같습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId ) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
mint와 burn
mint와 burn함수는 ERC-721 표준에서는 제공되지 않고, 프로젝트의 특정 요구에 따라 사용자 정의 함수로 구현되어야 합니다. 따라서 mint와 burn기능이 필요한 경우, ERC-721 FNT를 개발하는 프로젝트에서는 해당 기능을 자체적으로 추가하여 스마트계약을 구현해야 합니다.
*Openzeppelin에서 제공하는 ERC20의 경우 _mint함수와 _burn함수가 존재합니다.
_mint
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId, 1);
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
_burn
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
owner = ERC721.ownerOf(tokenId);
delete _tokenApprovals[tokenId];
unchecked {
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
_beforeTokenTransfer이란?
위에서 mint와 burn을 보면 _beforeTokenTransfer이 실행되는 것을 알 수 있습니다. _beforeTokenTransfer는 ERC-721 토큰의 전송이 이루어지기 전에 호출되는 내부 함수입니다. 이 함수는 ERC-721 토큰 컨트랙트에서 사용자 정의 로직을 실행하기 위한 훅(hook)으로 사용됩니다.
ERC-721 표준은 _beforeTokenTransfer 함수를 정의하지 않습니다. 그러나 많은 ERC-721 구현에서 mint와 burn 혹은 transfer을 하기 위하 이 함수를 추가로 구현하여 토큰 전송 전에 추가적인 동작을 수행할 수 있도록 합니다. 이 함수는 발송자(또는 소유자)가 ERC-721 토큰을 다른 주소로 전송하기 전에 실행됩니다.
주요 사용 사례는 다음과 같습니다.
1. 전송 제약사항: `_beforeTokenTransfer` 함수를 사용하여 토큰 전송 시 발송자와 수신자의 주소, 토큰 ID 등에 대한 추가적인 제약사항을 검사할 수 있습니다. 예를 들어, 특정 주소에 대한 전송을 제한하거나 전송 조건을 체크할 수 있습니다.
2. 이벤트 발행: `_beforeTokenTransfer` 함수를 사용하여 토큰 전송 이벤트 전에 사용자에게 알림을 보내거나 이벤트를 발행할 수 있습니다. 예를 들어, 토큰 전송에 대한 로그를 생성하거나, 특정 이벤트가 발생했음을 통지할 수 있습니다.
3. 추가 작업 수행: `_beforeTokenTransfer` 함수를 사용하여 토큰 전송 이전에 추가적인 작업을 수행할 수 있습니다. 예를 들어, 수신자 컨트랙트에 대한 초기화 작업을 수행하거나, 특정 데이터를 업데이트할 수 있습니다.
`_beforeTokenTransfer` 함수는 ERC-721 토큰 컨트랙트의 내부 함수로, 토큰 전송 이전에 호출되는 지점을 제공합니다. 이 함수를 커스터마이즈 하여 원하는 로직을 추가할 수 있습니다. 구체적인 동작은 개별적인 ERC-721 구현에 따라 다르며, 사용자 정의 함수로서 이벤트를 통해 투명하게 작동하도록 설계됩니다.
전체 코드
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract ERC721{
string public override name;
string public override symbol;
mapping(address => uint) private _balances;
mapping(uint => address) private _owners;
mapping(uint => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
function balanceOf(address _owner) public override view returns (uint) {
require(_owner != address(0), "ERC721 : balance query for the zero address");
return _balances[_owner];
}
function ownerOf(uint _tokenId) public override view returns (address) {
address owner = _owners[_tokenId];
require(owner != address(0), "ERC721 : owner query for the nonexistent token");
return _owners[_tokenId];
}
function approve(address _to, uint _tokenId) external override {
address owner = _owners[_tokenId];
require(_to != owner, "ERC721 : approval to current owner");
require(msg.sender == owner || isApprovedForAll(owner, msg.sender));
_tokenApprovals[_tokenId] = _to;
emit Approval(owner, _to, _tokenId);
}
function getApproved(uint _tokenId) public override view returns (address) {
require(_owners[_tokenId] != address(0));
return _tokenApprovals[_tokenId];
}
function setApprovalForAll(address _operator, bool _approved) external override {
require(msg.sender != _operator);
_operatorApprovals[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
function isApprovedForAll(address _owner, address _operator) public override view returns (bool){
return _operatorApprovals[_owner][_operator];
}
function _isApprovedOrOwner (address _spender, uint _tokenId) private view returns(bool) {
address owner = _owners[_tokenId];
require(owner != address(0));
return (_spender == owner || isApprovedForAll(owner, _spender) || getApproved(_tokenId) == _spender);
}
function transferFrom(address _from, address _to, uint _tokenId) external override {
require(_isApprovedOrOwner(_from, _tokenId));
require(_from != _to);
_beforeTokenTransfer(_from, _to, _tokenId);
_balances[_from] -= 1;
_balances[_to] += 1;
_owners[_tokenId] = _to;
emit Transfer(_from, _to, _tokenId);
}
function tokenURI(uint256 _tokenId) external override virtual view returns (string memory) {}
function _mint(address _to, uint _tokenId) public {
require(_to != address(0));
address owner = _owners[_tokenId];
require(owner == address(0));
_beforeTokenTransfer(address(0), _to, _tokenId);
_balances[_to] += 1;
_owners[_tokenId] = _to;
emit Transfer(address(0), _to, _tokenId);
}
function _beforeTokenTransfer(address _from, address _to, uint _token) internal virtual {}
}
'BlockChain > solidity' 카테고리의 다른 글
[Solidity] ERC-2771이란? (ERC-2771Context란?) (0) | 2023.07.02 |
---|---|
[Solidity] EIP-712란? (0) | 2023.07.02 |
[Solidity] ERC20 Permit이란? (0) | 2023.06.17 |
[Solidity] ERC-1155란? (0) | 2023.05.18 |
[Solidity] ERC20이란? (ERC20 토큰 발행) (0) | 2023.05.14 |