본문 바로가기
Blockchain/Ethereum

[Ethereum] ERC-721 소스 분석(1) - 인터페이스

by AustinProd 2022. 3. 23.

ERC-721 인터페이스

ERC-721 인터페이스에 명시된 함수는 ERC-20에 비해 상대적으로 많은 편이다. 토큰 정보 확인부터 전송, 권한 설정, 주소 추적 기능이 명시되어 있다. 

 

인터페이스 함수 목록

 

  • balanceOf : 파라미터로 전달받은 주소가 보유하고 있는 NFT 수량 반환
  • ownerOf : 파라미터로 전달 받은 NFT 토큰 주소를 소유하고 있는 주소 반환
  • safeTransferFrom : 전전송받을 주소(_to)가 NFT 토큰을 받을 수 있는지 검증하고 전송 진행(주소 종류가 EOA, CA에 따라 전달 파라미터 형식이 달라진다. 아래 코드 참조)
  • transferFrom : NFT 소유 주소로부터 다른 주소로 토큰 전송
  • approve : 파라미터로 전달 받은 주소에 NFT 전송 권한 부여
  • setApprovalForAll : 파라미터로 전달 받은 주소에 모든 NFT 전송 권한 부여 및 해제
  • getApproved : 파라미터로 전달 받은 NFT 토큰 주소에 대한 권한이 있는 주소 반환
  • isApprovedForAll : setApprovalForAll 함수 실행 권한 설정

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ERC-721 인터페이스
interface ERC721 {
  /************************************ 인터페이스 함수 ************************************/
  // 해당 주소가 보유하고있는 NFT 수량 반환
  function balanceOf(address _owner) external view returns (uint256);

  // NFT를 소유하고 있는 주소 반환
  function ownerOf(uint256 _tokenId) external view returns (address);

  // 전송받을 주소(_to)가 ERC-721 토큰을 받을수 있는지 검증하고 전송 [지갑(EOA, Externally Owned Account)에 전송] 
  function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

  // 전송받을 주소(_to)가 ERC-721 토큰을 받을수 있는지 검증하고 전송[컨트렉트(CA, Contract Account)에 전송] 
  function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

  // NFT 소유자로부터 다른 주소로 NFT 토큰 전송
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

  // 특정 주소에 NFT 전송 권한 부여
  function approve(address _approved, uint256 _tokenId) external payable;

  // 특정 주소에 NFT 소유자가 모든 NFT 전송 권한 부여 및 해제
  function setApprovalForAll(address _operator, bool _approved) external;

  // 특정 토큰 전송 권한을 갖고 있는 주소 반환
  function getApproved(uint256 _tokenId) external view returns (address);

  // setApprovealForAll 권한 여부 반환(true/false)
  function isApprovedForAll(address _owner, address _operator) external view returns (bool); 

  /************************************     이벤트     ************************************/
  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);  
}

 

ERC-165 인터페이스

ERC-165 인터페이스를 구현한 컨트렉트는 해당 컨트렉트에서 지원(Support)하는 인터페이스를 확인할 수 있게 된다. 인터페이스 식별자(Identifier) 값이 참/거짓으로 매핑되어 관리된다. ERC-165에 명시된 인터페이스 식별자를 전달하면 지원 여부를 true / false 값으로 확인할 수 있다.

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ERC-165 인터페이스 : 스마트 컨트렉트 탐색(Detecting) 표준
// https://eips.ethereum.org/EIPS/eip-165
interface ERC165 {
  /*
   * @dev 특정 인터페이스가 스마트 컨트렉트에 포함되어 있는지 확인(가비스 30,000 이하)
   * @param _interfaceID : ERC-165에 명시된 인터페이스 식별자(Identifier)
   * @return true/false : 지원가능 유무
   */
  function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}

 

Supports Interface 컨트렉트

ERC-165 인터페이스를 구현해 ERC-721 토큰에 상속되어 사용되는 유틸리티 컨트렉트다. 인터페이스 지원(Support) 여부를 바이트 타입 인터페이스 식별자 값과 bool 타입 값으로 매핑하여 관리한다. 

 

ERC-165 인터페이스에 대한 설정을 생성자에서 기본적으로 지정해주며, 개발하고자 하는 ERC-721에 따라 추가적인 설정이 가능하다.

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Import
import "./ERC165.sol"; // ERC165 인터페이스

// 스마트 컨트렉트 인트페이스 탐색(Detecting) 유틸 (ERC-165 인터페이스 구현)
contract SupportsInterface is ERC165 {

  // 지원 인터페이스 매핑 (인터페이스 바이트코드 -> true/false)
  // 0xffffffff 바이트 코드에 true 값 금지
  mapping(bytes4 => bool) internal supportedInterfaces;

  // 생성자 -> ERC-165 인터페이스 바이트코드를 고정값으로 입력 (가스비 절감)
  constructor() {
    supportedInterfaces[0x01ffc9a7] = true;
  }

  /*
   * @dev 이 컨트렉트에 어떤 인터페이스를 지원하지는 확인
   * @param _interfaceID : 인터페이스 바이트코드
   * @return true/false : 지원가능 유무
   */
  function supportsInterface(bytes4 _interfaceID) external override view returns (bool) {
    return supportedInterfaces[_interfaceID];
  }

 

ERC-721 Receiver (지갑 인터페이스)

이 인터페이스는 컨트렉트에 토큰이 락업되는 상황을 방지한다. 만약 토큰을 받은 컨트렉트가 다른 컨트렉트로 토큰 전송이 불가능한 상태라면 문제가 된다. 특정 계정에 한 해 토큰 유통이 막혀버리는 상황이 발생하는 것이다.

 

Receiver 인터페이스는 ERC-721 토큰 컨트렉트에서 토큰 전송(safeTransferFrom 함수)이 이루어질 때 받는 계정이 토큰을 전송할 수 있는 계정인지 아닌지에 대한 검증 표준을 제공한다. 해당 인터페이스를 구현한 컨트렉트는 지갑 및 옥션과 같은 서비스 로직을 구현할 수 있다.

 

NFT 토큰 전송(tranfer)이 이루어졌을 때, 토큰을 전달받은 계정(recipient) 쪽에서 이 인터페이스 함수를 호출하게 된다. 해당 함수는 토큰 전달 주소와 받는 주소, 토큰 ID, 기타 바이트 코드를 인자값으로 받는다. 이때, 인자값들이 데이터 타입에 맞게 입력되었는지를 검증한다.

 

반환값은 함수 식별자(Identifier)이며 bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")) 형식이다. 해당 인터페이스를 구현한 ERC-721 토큰 컨트렉트에서 Magic Value라는 함수 식별자에 대한 상수값(0x150b7a02)과 일치하는지 비교한다.

 

이 인터페이스 구현에 특징에 대해 아래와 같이 정리할 수 있다.

 

  • 컨트렉트 주소는 항상 msg.sender과 동일
  • Safe Transfer 기능을 위해 해당 인터페이스 구현 필수
  • NFT 토큰 전송에서 영수(receipt) 처리
  • 유효하지 않은 토큰 전송에 대해 거절(reject) 및 롤백(revert) 처리 (예외 처리)

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// 지갑 인터페이스 : Safe Transfer 허용
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
interface ERC721TokenReceiver {
  /*
   * @param _operator : `safeTransferFrom` 함수를 호출한 주소
   * @param _from : NFT 토큰 전소유자
   * @param _tokenId : NFT 토큰 아이디
   * @param _data : 추가 데이터(정해진 포멧은 없음)
   * @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 반환
   */
  function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
}

 

Address Utills

주소(Address) 관련 유틸리티 라이브러리로 주어진 주소값이 유효한지 검증한다. 주소의 해쉬값이 유효한 값인지 이더리움 가상머신(EVM)에서 해석하여 결과를 반환한다.

 

EIP-1052라는 이더리움 가상머신의 Opcode를 사용하는데, 코드 상에서 extcodehash로 표기되어 있음을 확인할 수 있다.

이 라이브러리의 유효성 검증은 주소 해쉬값이 공백인지 아닌지만 검증한다. 0x0 혹은 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 값은 주소가 존재하지 않음을 나타내는 해쉬값으로 아래 코드에서 단순 비교를 통해 결과를 반환함을 확인할 수 있다.

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// 주소(Address) 관련 유틸리티 라이브러리
// EIP-1052 규칙을 따름
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
library AddressUtils {
  /*
   * @dev 지정한 주소값이 유효한 컨트렉트인지 검증
   * @param _addr : 검증하고자하는 주소값
   * @return addressCheck : 검증 여부를 true/false로 반환
   */
  function isContract(address _addr) internal view returns (bool addressCheck) {

    /* 
     * 코드는 생성자 실행이 완료되고 나서 저장되므로, 생성 중인 컨트랙트에 대해 0을 반환하는 extcodesize에 의존
     * 
     * EIP-1052에 따라 "0x0"값은 존재하지 않는 어카운트에 대한 결과값이며,
     * "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"값은 `keccak256('')`와 같은 코드가 없는 어카운트의 결과값
     */
    bytes32 codehash;
    bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

    assembly { codehash := extcodehash(_addr) }
    addressCheck = (codehash != 0x0 && codehash != accountHash);
  }
}

 

'Blockchain > Ethereum' 카테고리의 다른 글

[Ethereum] ERC-721 소스 분석(3) - 메타 데이터  (0) 2022.03.25
[Ethereum] ERC-721 소스 분석(2) - 토큰  (0) 2022.03.24
[Ethereum] ERC-721  (0) 2022.03.23
[Ethereum] ERC-20  (0) 2022.03.23
[Ethereum] ERC란?  (0) 2022.03.22

댓글