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;
_balancesuint256 ⇒ (userAddress ⇒ uint256)) 형태의 map_operatorApprovalsaddress ⇒ (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에는 없다.