-
ERC721 구현Blockchain 2022. 1. 9. 11:45반응형
ERC721 Interface
NFT 를 위한 인터페이스를 eip721 에서 규약 하고 있으며, 해당 인터페이스를 직접 구현 해보도록 한다.
개발시 payable 키워드는 필요한 경우에만 사용 하도록 하고, 불필요한 함수들에서 제거 하도록 한다.
또한 external 을 public 으로 변경하여 구현 하도록 한다.
참고: 인터페이스 전체 함수를 구현 하지는 않고, 기본적인 함수만 구현 하도록 한다.EIP-721
EIP721 인터페이스는 아래와 같다.
pragma solidity ^0.4.20; 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) public view returns (uint256); function ownerOf(uint256 _tokenId) public view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) public; function safeTransferFrom(address _from, address _to, uint256 _tokenId) public; function transferFrom(address _from, address _to, uint256 _tokenId) public ; function approve(address _approved, uint256 _tokenId) public; function setApprovalForAll(address _operator, bool _approved) public; function getApproved(uint256 _tokenId) public view returns (address); function isApprovedForAll(address _owner, address _operator) public view returns (bool); }
ERC721 구현
ERC721Impl.sol
ERC721 구현 - mint 함수
ERC721 을 상속받아서 구현
contract ERC721Impl is ERC721 { //토큰아이디와 소유자 주소 매핑 mapping (uint256 => address) tokenOwner; //소유자 토큰 개수 매핑 mapping (address => uint256) ownedTokensCount; function mint(address _to, uint _tokenId) public { //토큰 아이디를 토큰 주소로 매핑 시켜 준다. tokenOwner[_tokenId] = _to; //소유자 토큰 개수를 증가 시켜 준다. ownedTokensCount[_to] += 1; } }
ERC721 구현 - balanceOf 함수
ERC721 을 상속한 ERC721Impl 클래스에 구현 하도록 한다.
사용자의 주소에 매핑된 토큰의 개수를 리턴 하도록 구현function balanceOf(address _owner) public vioew returns (uint256) { return ownedTokensCount[_owner]; }
ERC721 구현 - ownerOf 함수
토큰아이디로 소유자 주소 리턴하도록 한다.
function ownerOf(uint256 _tokenId) public view returns (address){ return tokenOwner[_tokenId]; }
ERC721 구현 - transferFrom 함수
유효성 검사 및 토큰 소유자의 주소와 갯수를 증감 / 차감 하도록 구현
function transferFrom(address _from, address _to, uint256 _tokenId) public { address owner = ownerOf(_tokenId); //함수를 호출한 계정이 소유자 인지 검증 require(msg.sender == owner || getApprove(_tokenId) == msg.sender || isApproveForAll(owner, msg.sender)); //비어있는지 검증 require(_from != address(0)); require(_to != address(0)); //토큰의 갯수를 차감 ownedTokensCount[_from] -= 1; //토큰의 아이디의 기존 주소를 제거 tokenOwner[_tokenId] = address(0); //전송받은 소유자의 토큰 갯수 증가 ownedTokensCount[_to] += 1; //토큰소유자의 새로운 소유자로 매핑 tokenOwner[_tokenId] = _to; }
ERC721 구현 - safeTransferFrom 함수
해당 컨트랙이 토큰을 받을수 있는지 검증하기 위한 ERC721Receiver 인터페이스 구현
ERC721Receiver
interface ERC721TokenReceiver { function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) public returns(bytes4); } //ERC721TokenReceiver를 구현한 컨트랙트 contract ERC721TokenReceiverImpl is ERC721TokenReceiver { function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) public returns(bytes4) { //keccak256 에 함수시그니쳐를 넘겨주면 0x150b7a02 값을 리턴 하게 된다. return bytes4(bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))); } } //컨트랙 계정을 구분하고, 컨트랙이 토큰을 받을수있는지 확인 할수 있는 전송 함수 function safeTransferFrom(address _from, address _to, uint256 _tokenId) public { transferFrom(_from,_to, tokenId); if(isContract(_to) { //ERC721 인터페이스 검증을 위해 아래 리턴받은 값이 인터페이스 식별값과 동일한지 비교하도록 한다. //참고: ERC721TokenReceiver 인터페이스 bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, ''); require(returnValue == 0x150b7a02); } } function isContract(address _addr) private view returns (bool) { uint256 size; //extcodesize 를 사용하여 해당 주소의 사이즈가 0보다 크면 true //참고1: extcodesize 는 계정의 코드 사이즈를 반환 한다. 현재 실행 환경의 컨트랙트를 대상으로 한다. //참고2: assembly 는 solidity 에서 evm 의 low level 연산을 수행 할수있도록 도와준다. assembly {size:=extcodesize(_addr)} return size >0; }
ERC721 구현 - approve 함수
소유자의 토큰을 다른 계정이 대신 전송 해주도록 승인 해주는것
토큰 전송 권한이 있는 (승인받은) 계정 정보를 담을 수 있도록 매핑 정의
mapping (uint256 => address) tokenApprovals;
전송 승인 권한 함수 구현
function approve(address _approved, uint256 _tokenId) public { address owner = ownerOf(_tokenId); //소유자 계정이 아니어야 한다. require(_approved != owner); //호출한 계정이 토큰 아이디의 소유자여야 한다. require(msg.sender == owner); tokenApprovals[_tokenId] = _approved; }
ERC721 구현 - getApprove 함수
토큰 전송 승인 권한
function getApprove(uint256 _tokenId) public view returns (address) { return tokenApprovals[_tokenId]; }
ERC721 구현 - setApprovalForAll 함수
토큰의 전송 권한을 여러명에게 넘겨주기 위해 매핑 선언 및 함수 구현
//누가 누구에게 권한 부여를 했는가를 정의하는 매핑 정보 (여러명에게 권한을 부여하기 위해 정의) mapping (address => mapping(address => bool)) operatorApprovals; function setApprovalForAll(address _operator, bool _approved) public { //권한을 부여받게될 operator 계정이 현재 함수를 호출한 계정인지 확인 require(_operator != msg.sende operatorApprovals[msg.sender][_operator] = _approved; }
ERC721 구현 - isApprovalForAll 함수
토큰의 소유자가 오퍼레이터에게 권한을 부여했는지 검증 하는 함수
function isApprovalForAll(address _owner, address _operator) public view returns (bool) { return operatorApprovals[_owner][_operator]; }
ERC165 Interface
ERC165 의 목적은 컨트랙트가 어떤 인터페이스를 상속 받고 있는지 확인 하는 용도이다.
interface ERC165 { function supportsInterface(bytes4 interfaceID) external view returns (bool); }
ERC165 Interface 구현
//인터페이스 식별자 매핑 정보 mapping (bytes4 => bool) supportedInterfaces; constructor() public { //ERC721 인터페이스의 식별자 키 값을 0x80ac58cd 로 정의하고 값을 true 로 함으로써 ERC721 인터페이스를 구현 하고 있는것을 생성자를 통해 정의 함. supportedInterfaces[0x80ac58cd] = true; } function supportsInterfacebytes4 interfaceID) public view returns (bool) { return supportedInterfaces[interfaceID]; }
반응형'Blockchain' 카테고리의 다른 글
Smart contract Dev setup (0) 2022.01.09