내맘대로 Study/블록체인

[클레이튼] ERC-721 개발

jinkwon.kim 2022. 4. 25. 19:19
728x90
반응형

ERC-721 interface 소스 코드

pragma solidity ^0.4.20;

/// @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 data) external payable;

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    function approve(address _approved, uint256 _tokenId) external payable;

    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);
}

 

Token의 전송

1. 일반 계정으로 전송.

    - 유져들간의 token 전송 입니다.

2. contract 계정으로 전송

    - contract 을 배포했을때 나오는 주소를 contract 계정이라고도 합니다.

    - token을 전송받은 contract가 token을 다루는 기능이 없으면 token을 분실이 발생 합니다. 

      (구제할 방법이 없음)

3. token 승인이란?

    - 소유자의 token을 다른 계정이 대신 전송 할 수 있도록 권한을 부여하는것

      Ex) A가 B에세 token을 맏겨서 B가 token을 전송 할 수 있게 권하는 주는것

    - 일반 계정에 권한을 부여해서 다른 계정으로 대신 전송하는 기능을 구현

 

ERC-721 interface 구현 Code 해설 

pragma solidity ^0.4.20;

/// @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) 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);
}

interface ERC721TokenReceiver {
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) public returns(bytes4);
}

contract ERC721Implementation is ERC721 {

    // token의 Id를 키로해서 계정을 리턴하는  mapping
    // token의 주인이 누구인지 확인해 준다.
    mapping(uint256 => address) tokenOwner;

    // address를 키로 사용하여 uint256를 value로 mapping한다.
    // 계정 주소를 입력하면 해당 계정이 몇개의 token을 가지고 있나 숫자로 리턴
    mapping(address => uint256) ownedTokenCount;
    //토큰 발생 함수 
    // mint의 뜻은 발생한다 이다.
    // _to: 발생된 token을 누가 소유하게 될것 인지 명시
    // _tokenId : 몇번째 token 인지 명시
    //            tokenId는 차례대로 1씩 증가한다.
    mapping(uint256 => address) tokenApprovals;

    // 누가 누구에게 권한 부여를 했는가를 의미한다.
    mapping(address => mapping(address => bool)) operatorApproavlas;

    function mint(address _to, uint _tokenId) public {
        //_tokenId를 tokenOwner의 키로하여 _to를 mapping
        tokenOwner[_tokenId] = _to;
        ownedTokenCount[_to] += 1;
    }

    // 계정의 token 개수를 구한다.
    function balanceOf(address _owner) public view returns (uint256) {
        return ownedTokenCount[_owner];
    }

    // token의 주인이 누구인지 반환한다.
    function ownerOf(uint256 _tokenId) public view returns (address) {
        return tokenOwner[_tokenId];
    }

    // tokenId를 가진 token을 소유하고있는from 계정에서to 계증으로 옮기겠다. 
    function transferFrom(address _from, address _to, uint256 _tokenId) public {
        // tokenId의 소유자 계정을 불러온다.
        address owner = ownerOf(_tokenId);

        // msg.sender : 함수를 호출한 계정 
        // 1. require를 하용하여 함수를 호출한 계정과 owner가 일치하는지 확인
        // 2. tokenId를 대신 전달 할수 있는 권한을 가진 계정인지 확인
        require(msg.sender == owner || getApproved(_tokenId) == msg.sender || isApprovedForAll(owner, msg.sender));

        // from과 to에 대항 검증
        // address(0) 은 비어있다를 의미한다.
        require(_from != address(0));
        require(_to != address(0));

        //from의 token 개수를 하나 줄임
        ownedTokenCount[tokenOwner[_tokenId]] -= 1;
        //tokenId의 소유권을 삭제
        tokenOwner[_tokenId] = address(0);

        //token의 새로운 소유자 지정
        ownedTokenCount[_to] +=1;
        tokenOwner[_tokenId] = _to;
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) public {
        transferFrom(_from, _to, _tokenId);
        // _to 주소가 contract인지 확인 하는 과정
        if (isContract(_to)) {
            //contract가 token을 받을수 있는 contract 인지 판단이 필요.
            // ERC721TokenReceiver(_to) 의 해석 : 
            //     _to 주소에 ERC721TokenReceiver interface가 존재 한다라는 뜻    
            // ERC721TokenReceiver(_to).onERC721Received
            //     onERC721Received가 구현 되어 있다면 magic value(function signature) 를 리턴 한다 
            bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, '');
            require(returnValue == 0x150b7a02);
        }
    }

    // _approved : 권한을 받게 되는 계정
    // tokenId를 사용할 수 있는 계정을 추가한다. 
    function approve(address _approved, uint256 _tokenId) public {
        //1 . 권한을 받게되는 계정이 이미 tokenId를 소유하고 있지 않는 확인 한다.
        //유효성을 확인한다.
        address owner = ownerOf(_tokenId);
        // 권한을  받게 되는 계정이 소유자 계정이 아니어야 한다.
        require(_approved != owner);
        // aprrove를 호출한 계정이 token의 소유자여야 한다.
        require(msg.sender == owner);

        tokenApprovals[_tokenId] = _approved;
    }

    // 내가 가진 모든 토큰에 대한 전송 권한을 준다.
    function setApprovalForAll(address _operator, bool _approved) public {
        // 내가 나한테 권한을 주는것을 방지 
        require(_operator != msg.sender);

        // 누가 누구에게 권한을 줄지를 설정
        // msg.sender가 _operator에게 권한을 부여할 것인지를 설정
        operatorApproavlas[msg.sender][_operator] = _approved;
    }

    // _owner가 _operator에게 권한을 부여했는지 확인 
    function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
        return operatorApproavlas[_owner][_operator];
    }

    // token의 
    function getApproved(uint256 _tokenId) public view returns (address) {
        return tokenApprovals[_tokenId];
    }


    // address contract 계정인지 확인
    function isContract(address _address) private view returns (bool) {
        uint256 size;
        // 주소 contract 인지 확인을
        // extcodesize에 주소를 넘겨서 린턴하는게 
        //     0 이면 일반계정, 
        //     0 보다 크면 contract 계정
        assembly { size:= extcodesize(_address)}
        return size > 0;
    }


    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) public {
        transferFrom(_from, _to, _tokenId);
        // _to 주소가 contract인지 확인 하는 과정
        if (isContract(_to)) {
            //contract가 token을 받을수 있는 contract 인지 판단이 필요.
            // ERC721TokenReceiver(_to) 의 해석 : 
            //     _to 주소에 ERC721TokenReceiver interface가 존재 한다라는 뜻    
            // ERC721TokenReceiver(_to).onERC721Received
            //     onERC721Received가 구현 되어 있다면 magic value(function signature) 를 리턴 한다 
            bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, data);
            require(returnValue == 0x150b7a02);
        } 
    }
}

contract Auction is ERC721TokenReceiver {
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) public returns(bytes4) {
        return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));

    }
}
728x90
반응형