Ethereum ERC-721 (NFT)

Hong·2022년 12월 9일
0
post-thumbnail





🕹️ Game Theory

유한게임과 무한게임
경제학에서 정의하는 게임이론과 별개로 수학에서는 게임이론(Mathmetical Game Theory)으로 유한게임과 무한게임의 개념을 제시한다
유한게임은 축구 경기처럼 승패가 명확하게 결정되며(무승부가 없다) 유한게임에 참가하는 Player들은 Game에 누가 참가하는지, 그들이 어떤 전략과 전력을 가지고 게임에 참가하는지 알고있다 때문에 상대방의 전략을 수시로 확인하며 나의 최선의 전략을 선택하여 행동한다
반면 인생이나 주식시장과 같은 무한게임은 게임의 룰이 굉장히 유동적이다 Game판에 누가 참가하는지 조차 모르며 누가 어떤 전략으로 게임에 참여하는지, 어떤 행동을 했는지, 해당 참가자가 패배자인지 승리자인지 판단할 수도 없으며 언제 게임이 끝나는지 정의할 수 없다
때문에 무한게임에서는 게임에 존속하는 Player들과 게임 판에서 떨어져 나간 Player들만이 존재한다

투자에서 가장 성공한 헤지펀드 매니저는 누구인가?
업계에 있는 많은 사람들은 이 질문에 대한 대답을 수익률, AUM(운용규모)로 판단하지 않는다
끝까지 살아남은 매니저가 헤지펀드 업계에서 가장 성공한 매니저다
They outlast everybody
무한게임에서는 승리자와 패배자를 명확하게 구분할 수 있는 기준이 없다

그런데 우리는 지금 어떤 Mind Set을 가지고 이 Game에 Player로서 참여하고 있나?
다른 플레이어들을 이기려고 하고
상대방이 어떤 카드를 가지고 있는지 확인할려하고
내가 패배자가 되지 않기 위해서 상대방과 경쟁한다
다른 사람들과 비교하고 다른 사람들에 비해 뒤쳐지지 않을려고 노력한다
하지만 현실은 축구나 체스가 아니기 때문에 승자와 패자가 시간이 지나며 계속해서 바뀌기도 하고
우리가 진리라고 생각했던 게임의 룰이 바뀌기도 한다
우리는 서로 의미없는 에너지 소모전을 치르고 있다

결국 승자와 패자가 없는 이 무한게임에서 오랜 시간동안 즐거움을 느끼며 게임에 참여하는 사람이 가장 큰 효용을 가져간다
책에서 무한게임을 유한게임으로 착각하며 열심히 살아가다 오래 버티지 못하고 정신적 질병을 앓는 사람들을 종종봐왔다 나도 사람인지라 종종 그런 식으로 삶을 살아가는데
지금 다른 사람들이 나보다 코드를 화려하게 작성한다는 것이 무엇이 중요한가 나는 내거 하며 안주하지 않는 마인드만 가지면 된다








🎨 What is ERC-721?

ERC-721은 증서라고 알려진 NFT의 표준안이다. NFT는 대체불가토큰(Non Fungible Token)의 약자로 대체 불가능한 토큰이라는 의미이다.
따라서 ERC-721로 발행되는 토큰은 대체 불가능하여 모두 제 각각의 가치(Value)를 갖고 있다.

ERC-721은 토큰 그 자체보다는 게임에 주로 쓰이는데, 대표적인 예로는 크립토키티(CryptoKitties)가 있다. 크립토키티의 고양이들은 제 각각 다른 생김새를 가지고 있다. 따라서 사용자가 보유하고 있는 고양이는 전 세계에서 단 하나밖에 없는 유일한 고양이가 된다.

반대로 ERC-20 토큰은 각각의 token이 동일한 가치를 지닌다. 주머니의 1000원의 지폐 5장이 같은 가치를 가지는 것과 같은 논리이다(특정 연도에 발행되어 수집 가치가 만들어진 지폐나 주화는 생각하지 않는다). 때문에 ERC-721과 달리 ERC-20은 대체가능한 토큰이 된다.

OpenZepplin에서 제공하는 ERC-721 (code)




balanceOf

balanceOf(address owner)

owner 주소를 입력받아 주소가 가지고 있는 NFT의 갯수를 반환한다

    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }



ownerOf

ownerOf(uint256 tokenId)

ERC-721로 발행된 모든 token에는 고유한 값인 tokenId가 있다
때문에 tokenId를 입력하면 해당 NFT를 들고 있는 address를 반환받을 수 있다

    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _ownerOf(tokenId);
        require(owner != address(0), "ERC721: invalid token ID");
        return owner;
    }



approve

approve(address to, uint256 tokenId)

입력한 tokenId(특정 NFT)를 다른 누군가가 사용할 수 있도록 승인한다

    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner or approved for all"
        );

        _approve(to, tokenId);
    }

    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }



getApproved

getApproved(uint256 tokenId)

tokenId가 누군가에게 승인(approve)된 상태면, 승인된 계정의 address를 반환한다

  // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

(생략)

    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }



setApprovalForAll

setApprovalForAll(address operator, bool approved)

이 함수를 호출한 msg.sender가 이 컨트랙트에서 가지고 있는 모든 NFT를 특정 address에게 승인한다

    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

💡 ERC-20에는 왜 setApprovalForAll 함수가 없을까?

내가 100개의 NFT를 소유하고 있고 이것을 Jack에게 보내고 싶다고 가정해보자
approve함수를 쓸 수 있겠지만 100번의 작업이 필요할 듯하다
이러한 불편함을 해결하기 위해 setApprovalForAll 함수가 생겼다
ERC-20 token에서는 이러한 불편함을 고민할 필요가 없다 내가 들고 있는 100개의 token이 모두 대체가능한 토큰이기 때문에 수량 100개를 입력한 후 transfer하면된다




isApprovedForAll

isApprovedForAll(address owner, address operator)

첫 번째 인자 owner가 두 번째 인자 operator 주소에게 setApprovalForAll() 함수를 통해 모든 NFT를 approve했는지의 여부를 return한다

  • return 값이 true인 경우 : owner의 모든 NFT가 operator에게 승인된 상태다
  • return 값이 false인 경우 : owner의 모든 NFT가 operator에게 전달되지 않은 상태다
    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

(생략)

    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }



transferFrom

transferFrom(address from, address to, uint256 tokenId)

from주소에서 to주소로 tokenId(NFT)를 옮긴다

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");

        _transfer(from, to, tokenId);
    }



    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

	

	function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId, 1);

        // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");

        // Clear approvals from the previous owner
        delete _tokenApprovals[tokenId];

        unchecked {
            // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
            // `from`'s balance is the number of token held, which is at least one before the current
            // transfer.
            // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
            // all 2**256 token ids to be minted, which in practice is impossible.
            _balances[from] -= 1;
            _balances[to] += 1;
        }
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId, 1);
    }

💡
하지만 실제로 NFT를 옮길 때는 transferFrom함수를 사용하지 않고 대신
safeTransferFrom함수를 사용한다
transferFrom함수는 NFT를 보내고자 하는 address(to)가 NFT를 받을 수 있는 계정인지 아닌지 판단하지 않기 때문이다(safeTransferFrom함수는 판단한다)

예를들어 Ethereum의 EOA계정이 아니라 CA계정에 NFT를 보내면 영영 NFT를 찾지 못하는 일이 벌어질 수 있다(CA계정에 NFT를 처리하는 code가 없다는 가정 하에)




_safeTransfer

    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }



    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }



🌊

ERC-20 token과 마찬가지로
remix와 metamask를 통해 ethereum mainnet에 배포가 가능하며
opensea에서도 확인할 수 있다

profile
Notorious

0개의 댓글