ERC-165, ERC-721, ERC-1155, ERC-4907

박재훈·2023년 3월 16일
0

ERC-165

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가 구현된 컨트랙트는 이 함수를 통해 타입을 가려낼 수 있다.

ERC-721

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

    • 컨트랙트 이름
    • ex) Apple
  • _symbol

    • 컨트랙트 심볼
    • ex) APL
  • _owners

    • (tokenIDuserAddress) 형태의 맵
  • _balances

    • (userAddresstokenCount) 형태의 맵
  • _tokenApprovals

    • (tokenIDoperatorAddress) 형태의 맵
  • _operatorApprovals

    • (userAddress ⇒ (operatorAddressboolean)) 형태의 맵
  • 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);
}

ERC-1155

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 ⇒ (userAddressuint256)) 형태의 map
    • 토큰 ID로부터 각 사용자가 가지고 있는 수량을 저장한다.
  • _operatorApprovals
    • (address ⇒ (addressbool)) 형태의 map
    • approve 된 operator를 확인할 수 있다.
    • operator가 사용자의 토큰들에 개별적으로 approve를 할 수 있던 ERC-721과 달리 무조건 모든 토큰에 approve 한다.
    • approve 관리가 이 map처럼 approve 되어 있는지 아닌지로 관리되기 때문에, 해당 유저가 새로운 토큰을 얻으면 operator는 그 새로운 토큰에도 접근할 수 있다.
  • _uri
    • ERC-1155 컨트랙트 자체에 대한 ID와 같은 개념이다.

유저가 가질 수 있는 토큰이 다량이기 때문에, 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 등의 기능을 제공한다.

ERC-4907

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에는 없다.

profile
코딩 좋아합니다

0개의 댓글