ERC165의 목적은 컨트랙이 어떤 인터페이스를 상속받고있나 확인하는 기능을 제공한다.
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
인터페이스 아이디를 인자로 전달하면 여부를 boolean으로 리턴한다.
인터페이스 아이디는 식별자이다.
식별자는 byte4 타입이며 magic value라고 부른다.
인터페이스마다 유니크한 식별자가 정해져있다.
식별자 구하는 방법은 다음과 같다.
bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
0x150b7a02와 같은 결과값은 함수명인 onERC721Received와 매개변수 타입들 address, address, uint256, bytes 들을 해시하여 4바이트로 자른 값이다. (keccak256()는 해시 함수이다.)
이렇게 리턴되는 매직 밸류를 함수 시그니처라고 부른다.
bytes4(keccak256("balanceOf(address)")) ^
bytes4(keccak256("ownerOf(uint256)")) ^
bytes4(keccak256("transferFrom(address,address,uint256)")) ^
bytes4(keccak256("safeTransferFrom(address,address,uint256)")) ^
bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")) ^
bytes4(keccak256("approve(address,uint256)")) ^
bytes4(keccak256("getApproved(uint256)")) ^
bytes4(keccak256("setApprovalForAll(address,bool)")) ^
bytes4(keccak256("isApprovedForAll(address,address)")) ^
bytes4(keccak256("isContract(address)"))
인터페이스 함수들을 각각 해싱, bytes4로 잘라내여 함수 시그니처 값을 구한 후 모두 XOR한다.
ERC721 시그니처 결과 = 0x8ac58cd
contract ERC721Implementation is ERC721 {
// 인터페이스 식별자를 키로 불리언을 리턴한다. 즉, ERC721Implementation이 특정 인터페이스를 쓰는지 확인할때 사용된다.
mapping (bytes4 => bool) supportedInterface;
// 생성자에서 ERC721 식별자를 맵핑에 저장
constructor() public {
supportedInterface[0x80ac58cd] = true;
}
// 외부에서 이 함수를 호출(ERC721 식별자를 인자로)하여 ERC721을 상속받았는지 확인 가능
function supportsInterface(bytes4 interfaceID) public view returns (bool){
return supportedInterface[interfaceID];
}
...
전체코드
pragma solidity >=0.4.24 <=0.5.6;
/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface ERC721 /* is ERC165 */ {
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);
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external ;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external ;
function transferFrom(address _from, address _to, uint256 _tokenId) external ;
function approve(address _approved, uint256 _tokenId) external ;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
interface ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
}
interface ERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
contract ERC721Implementation is ERC721 {
// 토큰의 아이디를 키값으로 해당 주소를 리턴
mapping (uint256 => address) tokenOwner;
// 계정 주소에 해당하는 토큰 수를 리턴
mapping (address => uint256) owendTokensCount;
mapping (uint256 => address) tokenApprovals;
// 누가 누구에게 권한 부여를 했는가에 대한 맵핑, 1:N으로 권한을 부여할 수 있음.
mapping (address => mapping (address => bool)) operatorApprovals;
// 인터페이스 식별자를 키로 불리언을 리턴한다. 즉, ERC721Implementation이 특정 인터페이스를 쓰는지 확인할때 사용된다.
mapping (bytes4 => bool) supportedInterface;
constructor() public {
supportedInterface[0x80ac58cd] = true;
}
// 토큰 발행
function mint(
address _to, // 발행된 토큰의 소유자
uint _tokenId // 몇 번째 토큰인지
) public {
tokenOwner[_tokenId] = _to;
owendTokensCount[_to] += 1; // 토큰 보유 개수 1 증가시킴
}
// 특정 계정의 토큰 수를 리턴
function balanceOf(address _owner) public view returns (uint256) {
return owendTokensCount[_owner];
}
// 토큰의 주인 주소를 리턴
function ownerOf(uint256 _tokenId) public view returns (address) {
return tokenOwner[_tokenId];
}
// 토큰 전송
function transferFrom(address _from, address _to, uint256 _tokenId) public {
// 이 함수를 호출한 계정이 _tokenId의 소유자인지 검사
address owner = ownerOf(_tokenId);
// 1.토큰의 소유자 계정이거나 2.특정 토큰에 권한이 있는 계정이 접근했을때 3.소유자의 전체 토큰의 전송 권한을 가진 계정이 호출했을때
require(msg.sender == owner || getApproved(_tokenId) == msg.sender || isApprovedForAll(owner, msg.sender));
// 빈 인자 검사
require(_from != address(0)); // address(0)는 비어있다는 의미
require(_to != address(0));
// 보내는 측의 토큰 보유 수량을 1차감
owendTokensCount[_from] -= 1;
// 기존 토큰 보유자의 토큰 소유 정보를 제거
tokenOwner[_tokenId] = address(0);
// 전달받는 측의 토큰 보유 수량을 1증가
owendTokensCount[_to] += 1;
// 전달받는 측에 토큰 소유정보 추가
tokenOwner[_tokenId] = _to;
}
// 토큰 안전 정송
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public {
transferFrom(_from, _to, _tokenId);
if(isContract(_to)){
bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, '');
require(returnValue == 0x150b7a02);
}
}
// 토큰 안전 전송2
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public {
transferFrom(_from, _to, _tokenId);
if(isContract(_to)){
bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, data);
require(returnValue == 0x150b7a02);
}
}
function approve(address _approved, uint256 _tokenId) public {
// 권한을 받게되는 계정은 토큰 오너가 아니여야 한다.
address owner = ownerOf(_tokenId);
require(_approved != owner);
// 토큰 주인이 호출해야함
require(msg.sender == owner);
tokenApprovals[_tokenId] = _approved;
}
function getApproved(uint256 _tokenId) public view returns (address) {
return tokenApprovals[_tokenId];
}
function setApprovalForAll(address _operator, bool _approved) external {
// _operator: 계정이 소유한 모든 토큰들을 대신 운영할 계정
// _approved: 권한 부여 여부
require(_operator != msg.sender);
operatorApprovals[msg.sender][_operator] = _approved;
}
function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
return operatorApprovals[_owner][_operator];
}
// 외부에서 이 함수를 호출(ERC721 식별자를 인자로)하여 ERC721을 상속받았는지 확인 가능
function supportsInterface(bytes4 interfaceID) public view returns (bool){
return supportedInterface[interfaceID];
}
function isContract(address _addr) private view returns (bool) {
uint256 size;
assembly { size:= extcodesize(_addr) }
return size > 0;
}
}
contract Auction is ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes memory _data) public returns(bytes4) {
return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
}
}
contract Auction is ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes memory _data) public returns(bytes4) {
return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
}
function checkSupportsInterface(address _to, bytes4 interfaceID) public view returns (bool) {
return ERC721Implementation(_to).supportsInterface(interfaceID);
}
}
ERC 표준은 여러가지가 있고 각 표준들은 인터페이스 형태로 정의되어있다.
컨트랙의 주소만 가지고 그 컨트랙트가 어떤 표준을 구현했는지 알수가 없기 때문에
그 부분을 알려주기 위해 ERC165의 supportsInterface() 함수를 사용하는것이다.