오늘 내가 배운 것
1. ERC721 표준 상태변수
2. ERC721 표준 메서드 분석하기
ERC721 표준 메서드 종류 및 역할
ERC721은 대체 불가능한 토큰(NFT)을 생성할 때 사용하는 ERC표준이다.
NFT는 객체 형태와 유사하기 때문에, 상태변수가 mapping 타입으로 되어있는 것이 많다.
1. ERC721 표준 상태변수
1. string private _name : 토큰의 이름을 저장한다.
2. string private _symbol : 토큰의 심볼(단위)을 저장한다.
3. mapping(uint256 => address) private _owners : _owner는 특정토큰에 대한 소유자 주소로 매핑이 되어있으며 형태는 아래와 같다.
{
"tokenId": "address"
}
4. mapping(address => uint256) private _balances : 계정과 계정이 소유하고 있는 토큰의 수량으로 매핑이 되어있으며, 계정을 이용해서 토큰의 개수를 구할 수 있다.
// 예를들어 "0x0000 계정에 NFT가 2개 있다면
{
"0x0000": 2
}
5. mapping(uint256 => address) private _tokenApprovals : 토큰 Id와 그 토큰 Id에 대한 권한이 있는 계정이 매핑되어 있으며, 토큰 Id를 이용해서 토큰 Id를 전송할 수 있는 계정을 확인할 수 있다.
{
"tokenId": "address"
}
6. mapping(address => mapping(address => bool)) private _operatorApprovals : 중첩 매핑으로 소유자의 토큰을 권한 받은 계정과 승인여부에 대해서 매핑이 되어있다.
승인 상태를 "_operatorApprovals[소유자계정][대리인계정]"으로 확인할 수 있다.
// 예를 들어 0x0000 계정이 0x1234 계정에게 토큰에 대한 권한을 주었다면
{
"0x0000": {
"0x1234": true
}
}
2. ERC721 표준 메서드 분석하기
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
using Address for address;
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
function _baseURI() internal view virtual returns (string memory) {
return "";
}
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _ownerOf(tokenId) != address(0);
}
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
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);
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
// Will not overflow unless all 2**256 token ids are minted to the same owner.
// Given that tokens are minted one by one, it is impossible in practice that
// this ever happens. Might change if we allow batch minting.
// The ERC fails to describe this case.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
owner = ERC721.ownerOf(tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
function _transfer(address from, address to, uint256 tokenId) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from] -= 1;
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
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);
}
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
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 {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
// solhint-disable-next-line func-name-mixedcase
function __unsafe_increaseBalance(address account, uint256 amount) internal {
_balances[account] += amount;
}
}
1. supportsInterface(bytes4 interfaceId) : 퍼블릭 함수 - 주어진 인터페이스 ID가 맞는지 확인하는 함수이다. ERC721 표준을 지켜서 NFT를 생성하게 되면 "0x80ac58cd" 값이 맞는지를 확인하는 것이고, T/F로 반환된다.
2. balanceOf(address owner) : 퍼블릭 함수 - 특정 주소의 NFT토큰의 개수를 구하는 함수이다. 해당 컨트랙트의 상태변수로 계정별 토큰의 개수를 관리하고 있다.
3. ownerOf(uint256 tokenId) : 퍼블릭 함수 - 특정 토큰 ID를 가지고 있는 계정의 주소를 반환하는 함수이다.
4. name() : 퍼블릭 함수 - NFT의 이름을 반환하는 함수이다.
5. symbol() : 퍼블릭 함수 - NFT의 심볼(단위)를 반환하는 함수이다.
6. tokenURI(uint256 tokenId) : 퍼블릭 함수 - 토큰 ID의 URI를 반환하는 함수이다. 보통 json이 업로드된 경로이다.
7. _baseURI() : 내부 함수 - 필요에 따라 baseURI를 지정하는 데 사용하는 함수이다.
8. approve(address to, uint256 tokenId) : 퍼블릭 함수 - "to"에게 토큰 ID에 해당하는 NFT를 거래할 수 있는 권한을 주는 함수이다.
9. getApproved(uint256 tokenId) : 퍼블릭 함수 - 토큰 ID의 권한을 가지고 있는 계정을 반환하는 함수이다. _tokenApprovals에서 값을 찾는다.
10. setApprovalForAll(address operator, bool approved) : 퍼블릭 함수 - 특정 주소가 가지고 있는 모든 NFT에 대한 권한을 부여할 때 사용한다.
11. isApprovedForAll(adress owner, address operator) : 퍼블릭 함수 - operator에게 owner가 모든 NFT의 권한을 승인했는지 확인할 때 사용하는 함수이다.
12. transferFrom(address from, address to, uint256 tokenId) : 퍼블릭 함수 - NFT를 전송할 때 사용하는 함수이다. (권한을 받은 계정이 사용하는 메서드이다.)
13. safeTransferFrom(address from, address to, uint256 tokenId) : 퍼블릭 함수 - 토큰을 안전하게 다른 주소로 이동하기 위해서 사용하는 메서드이다.
14. safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) : 퍼블릭 함수 - 토큰을 안전하게 다른 주소로 이동하고 데이터를 전달하는 함수이다.
15. _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) : 내부 함수 - 토큰을 안전하게 이동하는 내부 함수이다.
16. _ownerOf(uint256 tokenId) : 내부 함수 - tokenId를 이용해서 토큰의 소유자의 주소를 반환하는 함수이다.
17. _exists(uint tokenId) : 내부 함수 - 특정 tokenId가 존재하는지 확인하는 함수이다 (T/F)
18. _isApprovedOrOwner(address spender, uint256 tokneId) : 내부 함수 - token의 소유자이거나, 승인을 받은 계정인지를 확인하는 함수이다.
19. _safeMint(address to, uint256 tokenId) : 내부 함수 - 특정 주소에 토큰 ID를 안전하게 발행하는 함수이다.
20. _safeMint(address to, uint256 tokenId, bytes memory data) : 내부 함수 - 특정 주소에 토큰 ID를 안전하게 발행하고, 데이터를 전달하는 함수이다.
21. _mint(address to, uint256 tokenId) : 내부 함수 - 특정 주소로 토큰 Id를 발행하는 함수이다.
22. _burn(uint256 tokenId) : 내부 함수 - 특정 토큰 Id를 소각하는 함수이다.
23. _transfer(address from, address to, uint256 tokenId) : 내부 함수 - 토큰을 다른 주소로 이동하는 함수이다.
24. _approve(address to, uint256 tokenId) : 내부 함수 - 특정 주소에 tokenId에 대한 권한을 주는 함수이다.
25. _setApprovalForAll(address owner, address operator, bool approved) : 내부 함수 - 특정 주소에 대해서 모든 토큰을 승인하는 함수이다.
26. _requireMinted(uint256 tokenId) : 내부 함수 - tokenId가 발행되었는지 확인하는 함수이다. require()를 이용한 예외처리를 할 때 사용할 수 있다.
27. _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) : 내부 함수 - 수신하는 주소가 ERC721 인터페이스를 구현했는지 확인하는 함수이다.
28. _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) : 내부함수 - 토큰을 이동시키기 전에 실행하는 로직이며, 커스텀하여 사용할 수 있다. 4번째 인자값의 batchSize는 한 번에 보내게 되는 NFT의 수량이다.
29. _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) : 내부함수 - 토큰을 이동한 후에 실행하는 로직이며, 커스텀하여 사용할 수 있다. 4번째 인자값의 batchSize는 한 번에 보내게 되는 NFT의 수량이다.
30. __unsafe_increaseBalance(address account, uint256 amount) : 내부함수 - 특정 주소의 토큰 잔액을 증가시키는 내부 함수이다.
'💠BlockChain💠' 카테고리의 다른 글
NFT(Non-Fungible Token) 기초 (0) | 2023.06.02 |
---|---|
ERC20 표준 메서드 종류 및 역할 (0) | 2023.06.01 |
NFT(Non-Fungible Token)의 개념 (0) | 2023.05.31 |
Ethereum - 사과가게 만들기 (0) | 2023.05.30 |
Blockchain - Layer2 (레이어2) (0) | 2023.05.30 |
댓글