본문 바로가기

Blockchain

ERC721 구현

반응형

ERC721 Interface

EIP-721

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