openzeppelin-contracts/IERC165.sol at master · OpenZeppelin/openzeppelin-contracts
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
컨트랙트의 메소드 시그니쳐들을 XOR 한 값을 통해 특정 컨트랙트 형식에 맞는지 확인할 수 있다.
/*
* bytes4(keccak256('balanceOf(address)')) == 0x70a08231
* bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
* bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
* bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
* bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
* bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
* bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
* bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
* bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
*
* => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
* 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
*/
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
솔리디티 0.5.0에서는 위와 같이 직접 계산하여 하드코딩 하였으며,
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
v0.8.0에서는 위처럼 사용하도록 바뀌었다.
그래서 ERC-165를 구현하려면 supportsInterface(bytes4)
메소드를 구현하면 된다.
또한 ERC-165가 구현된 컨트랙트는 이 함수를 통해 타입을 가려낼 수 있다.
openzeppelin-contracts/ERC721.sol at master · OpenZeppelin/openzeppelin-contracts
1개의 ERC-721 컨트랙트는 1개 종류의 토큰만 표현 가능하다.
만약 사과
에 관한 ERC-721 컨트랙트를 만들었다면 그 컨트랙트는 사과
만 표현할 수 있다.
// 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;
_name
_symbol
_owners
tokenID
⇒ userAddress
) 형태의 맵_balances
userAddress
⇒ tokenCount
) 형태의 맵_tokenApprovals
tokenID
⇒ operatorAddress
) 형태의 맵_operatorApprovals
userAddress
⇒ (operatorAddress
⇒ boolean
)) 형태의 맵operator
: 특정 토큰에 대해 거래할 수 있는 권한을 가진 계정
1명의 유저에 대해 1명의 operator
만 설정이 가능하다.
operator
는 유저의 특정 토큰에 대한 권한만 가질 수도 있고, 전체 토큰에 대한 권한을 가질 수도 있다.
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);
// address의 밸런스 조회
function balanceOf(address owner) external view returns (uint256 balance);
// token을 소유한 owner 확인
function ownerOf(uint256 tokenId) external view returns (address owner);
// approve 필요
// data는 정해진 표준이 없는 단순 추가 데이터
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
-> emit Transfer
// approve 필요
function safeTransferFrom(address from, address to, uint256 tokenId) external;
-> emit Transfer
// approve 필요
function transferFrom(address from, address to, uint256 tokenId) external;
-> emit Transfer
// operator가 특정 token에 대해 transfer 할 수 있는 권한을 얻기 위함
function approve(address to, uint256 tokenId) external;
-> emit Approval
// operator가 유저의 모든 token에 approve 하게끔 설정
function setApprovalForAll(address operator, bool approved) external;
-> emit ApprovalForAll
// 특정 token이 approve 된 operator 확인
function getApproved(uint256 tokenId) external view returns (address operator);
// 모든 token에 approve 되어 있는지 확인
function isApprovedForAll(address owner, address operator) external view returns (bool);
Openzeppelin에서 제공하는 ERC-721의 internal function인 _mint
를 사용해 mint 기능을 구현할 수 있다.
이외에도 requireMint
, safeMint
, burn
등의 기능을 제공한다.
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);
}
openzeppelin-contracts/ERC1155.sol at master · OpenZeppelin/openzeppelin-contracts
1개의 ERC-1155 컨트랙트 내에 여러 종류의 토큰이 있을 수 있다.
ERC-20이나 ERC-721이 토큰의 이름, 심볼을 설정할 수 있던 것과 달리 ERC-1155는 포함하고 있는 토큰들의 속성(이름, 심볼) 설정은 따로 할 수 없으며 단순히 uint256 타입의 ID를 통해 관리한다.
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
// Mapping from account to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;
_balances
uint256
⇒ (userAddress
⇒ uint256
)) 형태의 map_operatorApprovals
address
⇒ (address
⇒ bool
)) 형태의 mapoperator
를 확인할 수 있다.operator
가 사용자의 토큰들에 개별적으로 approve를 할 수 있던 ERC-721과 달리 무조건 모든 토큰에 approve 한다._uri
유저가 가질 수 있는 토큰이 다량이기 때문에, ERC-1155에서는 ERC-20이나 ERC-721에서 사용되는 토큰을 다루는 함수에 대부분 추가적으로 배치 기능을 제공한다.
또한 transfer
가 없이 safeTransfer
만 존재한다.
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
// uri가 변경될 때 발생하는 것으로, 사용자 구현에 따른다.
event URI(string value, uint256 indexed id);
// 유저가 id에 해당하는 토큰을 보유한 개수
function balanceOf(address account, uint256 id) external view returns (uint256);
// 유저들이 각 ids의 원소에 해당하는 토큰을 보유한 개수들
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
) external view returns (uint256[] memory);
// operator approve
function setApprovalForAll(address operator, bool approved) external;
-> emit ApprovalForAll
// operator가 account에 대해 approve 되어 있는지 확인
function isApprovedForAll(address account, address operator) external view returns (bool);
// safe transfer
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
-> emit TransferSingle
// 여러 종류의 토큰을 한꺼번에 보내기 위함
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
-> emit TransferBatch
Openzeppelin에서 제공하는 ERC-1155의 internal function인 _mint
와 _mintBatch
를 사용해 mint 기능을 단일 또는 배치로 구현할 수 있다.
이외에도 burn
, burnBatch
등의 기능을 제공한다.
EIPs/eip-4907.md at master · ethereum/EIPs
interface IERC4907 {
// Logged when the user of an NFT is changed or expires is changed
/// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed
/// The zero address for user indicates that there is no user address
event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);
/// @notice set the user and expires of an NFT
/// @dev The zero address indicates there is no user
/// Throws if `tokenId` is not valid NFT
/// @param user The new user of the NFT
/// @param expires UNIX timestamp, The new user could use the NFT before expires
function setUser(uint256 tokenId, address user, uint64 expires) external;
/// @notice Get the user address of an NFT
/// @dev The zero address indicates that there is no user or the user is expired
/// @param tokenId The NFT to get the user address for
/// @return The user address for this NFT
function userOf(uint256 tokenId) external view returns(address);
/// @notice Get the user expires of an NFT
/// @dev The zero value indicates that there is no user
/// @param tokenId The NFT to get the user expires for
/// @return The user expires for this NFT
function userExpires(uint256 tokenId) external view returns(uint256);
}
NFT를 대여할 수 있는 컨트랙트 표준이다. 아직 Openzeppelin에는 없다.