ERC-20과 ERC-721 소스 코드 차이
포스트
취소

ERC-20과 ERC-721 소스 코드 차이

Erc-20과 Erc-721은 대체 가능한지로 구분이 되는데, 소스코드 단에서는 같은 명칭의 함수를 사용한다.
코드 단에서의 차이는 무엇이 있을까?


오픈 재플린의 깃헙 소스 코드를 기준으로 차이를 살펴보자.

참고

openZeppelin ERC-20 Github

openZeppelin ERC-721 Github


Function Mint

먼저 민트 함수를 살펴보자.

같은 기능이지만 코드를 보면 입력 값로직이 다르다.

  • ERC-20
    • 입력 값 : 주소(account), 발행량(amount)
    • 로직 : 주소로 발행량 전송
  • ERC-721
    • 입력 값 : 받는 주소(to), 토큰ID(tokenId)
    • 로직 : 토큰ID의 소유자를 수신 주소로 변경 & 전송

ERC-20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function _mint(address account, uint256 amount) internal virtual {
    require(account != address(0), "ERC20: mint to the zero address");

    _beforeTokenTransfer(address(0), account, amount);

    _totalSupply += amount;
    unchecked {
        // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
        _balances[account] += amount;
    }
    emit Transfer(address(0), account, amount);

    _afterTokenTransfer(address(0), account, amount);
}

ERC-721

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function _mint(address to, uint256 tokenId) internal virtual {
    require(to != address(0), "ERC721: mint to the zero address");
    require(!_exists(tokenId), "ERC721: token already minted");

    _beforeTokenTransfer(address(0), to, tokenId, 1);

    // Check that tokenId was not minted by `_beforeTokenTransfer` hook
    require(!_exists(tokenId), "ERC721: token already minted");

    unchecked {
        // Will not overflow unless all 2**256 token ids are minted to the same owner.
        // Given that tokens are minted one by one, it is impossible in practice that
        // this ever happens. Might change if we allow batch minting.
        // The ERC fails to describe this case.
        _balances[to] += 1;
    }

    _owners[tokenId] = to;

    emit Transfer(address(0), to, tokenId);

    _afterTokenTransfer(address(0), to, tokenId, 1);
}



Function Transfer

  • ERC-20
    • 입력 값 : 보내는 주소(from), 받는 주소(to), 보내는 양(amount)
    • 로직 : 보내는 주소에서 받는 주소로 보내는 양 만큼 전송
  • ERC-721
    • 입력 값 : 보내는 주소(from), 받는 주소(to), 토큰ID(tokenId)
    • 로직 : 소유자에 대한 권한처리 & 토큰ID를 받는 주소로 변경 그리고 전송


ERC-20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function _transfer(
    address from,
    address to,
    uint256 amount
) internal virtual {
    require(from != address(0), "ERC20: transfer from the zero address");
    require(to != address(0), "ERC20: transfer to the zero address");

    _beforeTokenTransfer(from, to, amount);

    uint256 fromBalance = _balances[from];
    require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
    unchecked {
        _balances[from] = fromBalance - amount;
        // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
        // decrementing then incrementing.
        _balances[to] += amount;
    }

    emit Transfer(from, to, amount);

    _afterTokenTransfer(from, to, amount);
}

ERC-721

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
}



Function Burn

  • ERC-20
    • 입력 값 : 주소(account), 소각할 양(amount)
    • 로직 : 주소에서 소각할 양 만큼 소각
  • ERC-721
    • 입력 값 : 토큰ID(tokenId)
    • 로직 : 소유자에 대한 권한처리 & 토큰ID의 소유자 정보 삭제 그리고 토큰 소각


ERC-20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function _burn(address account, uint256 amount) internal virtual {
    require(account != address(0), "ERC20: burn from the zero address");

    _beforeTokenTransfer(account, address(0), amount);

    uint256 accountBalance = _balances[account];
    require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
    unchecked {
        _balances[account] = accountBalance - amount;
        // Overflow not possible: amount <= accountBalance <= totalSupply.
        _totalSupply -= amount;
    }

    emit Transfer(account, address(0), amount);

    _afterTokenTransfer(account, address(0), amount);
}

ERC-721

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function _burn(uint256 tokenId) internal virtual {
    address owner = ERC721.ownerOf(tokenId);

    _beforeTokenTransfer(owner, address(0), tokenId, 1);

    // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
    owner = ERC721.ownerOf(tokenId);

    // Clear approvals
    delete _tokenApprovals[tokenId];

    unchecked {
        // Cannot overflow, as that would require more tokens to be burned/transferred
        // out than the owner initially received through minting and transferring in.
        _balances[owner] -= 1;
    }
    delete _owners[tokenId];

    emit Transfer(owner, address(0), tokenId);

    _afterTokenTransfer(owner, address(0), tokenId, 1);
}



코드를 살펴보면 ERC-20과 ERC-721에 대한 차이가 확연하다.
중요한 것은 ERC-20에서는 양(amount)가 중요하고, ERC-721에는 토큰ID(tokenId)가 중요하다.
이것을 보면 ‘대체가 가능’한 것과 ‘대체가 불가능’한 토큰의 차이를 확인할 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.