‘이더리움 댑 개발’ 세미나 44. ERC20, ERC721
이번 세미나에서는 대표적인 토큰 표준인 ERC20과 ERC721을 다룹니다.
ERC20은 대표적인 대체 가능(fungible) 토큰 표준이고, ERC721은 대표적인 대체 불가(non fungible) 토큰 표준입니다.
좀 더 복잡하지만 최대한 단순화 해보면, 이더리움 표준은 EIP(Ethereum Improvement Proposals)라는 형태로 제안되고, ERC(Ethereum Request for Comment)는 토큰 표준을 위한 EIP이고, 그중 ERC20과 ERC721이 가장 대표적이다라고 할 수 있습니다. ERC 뒤에 붙은 숫자는 몇 번째 EIP 였는지를 나타냅니다. ERC20은 EIP20에 해당합니다.
이더리움은 코인과 토큰을 구분하고 있는데, 블록체인 자체적(native)으로 발행되는 디지털 자산을 코인이라고 하고, 블록체인 상에서 스마트 컨트랙트로 발행되는 디지털 자산을 토큰이라고 합니다. 이더는 코인이고, 토큰 표준에 의해 발행되는 것들은 토큰이 됩니다. 코인과 토큰은 모두 암호화폐(cryptocurrency)라고 합니다.
ERC20
ERC20은 대체가능한 토큰 표준입니다. 우리가 일반적으로 사용하는 법정화폐는 대체가능합니다. 내가 가지고 있는 만원과 다른 사람이 가지고 있는 만원을 서로 바꾸어도 그 가치는 변하지 않습니다.
ERC20은 대체가능한 토큰을 발행하고 서로 주고 받을 수 있게 하려면 어떤 기능이 있어야 하는지에 대한 명세입니다.
- 어떤 토큰인지 구분할 수 있어야 합니다. 사용성을 위한 것으로 토큰 전송에는 없어도 됩니다. 선택적입니다.
- 이름, 심볼
- name, symbol
-
12function name() public view returns (string)function symbol() public view returns (string)
-
- name, symbol
- 이름, 심볼
- 일반 화폐처럼 더 작은 단위로 나눌 수도 있어야 합니다. 수량은 uint로 작성되기 때문에 소수점은 의미가 없습니다. 사용성을 위한 것입니다. 선택적입니다.
- 소수점 몇 째자리 까지 나눌 것인지
- decimals
-
1function decimals() public view returns (uint8)
-
- decimals
- 소수점 몇 째자리 까지 나눌 것인지
- 누가 얼마만큼의 토큰을 가지고 있는지가 관리되어야 합니다.
-
1function balanceOf(address _owner) public view returns (uint256 balance)
-
- 토큰 전송이 가능해야 합니다.
-
1function transfer(address to, uint256 value) public returns (bool success)
-
ERC20은 여기에 더해, 총 공급량을 정하도록 하고 있으며 토큰 전송을 위임할 수 있도록 하고 있습니다.
- 총 공급량이 관리되어야 합니다.
- totalSupply
-
1function totalSupply() public view returns (uint256)
-
- totalSupply
- 토큰 전송을 위임할 수 있습니다.
- 어떤 계정에게 얼마만큼의 토큰 전송을 위임할 것인지를 설정합니다.
-
1function approve(address spender, uint256 value) public returns (bool success)
-
- 위임 받은 계정에서 보내는 것이니 누구의 계정(위임한 계정)에서 보낼 것인지가 추가적으로 명시되어야 합니다.
-
1function transferFrom(address from, address to, uint256 value) public returns (bool success)
-
- 위임한 수량 중에 얼마만큼 남았는지가 관리되어야 합니다.
-
1function allowance(address owner, address spender) public view returns (uint256 remaining)
-
- 어떤 계정에게 얼마만큼의 토큰 전송을 위임할 것인지를 설정합니다.
ERC20은 전송과 위임에 대해 다음과 같은 이벤트 발생을 명시하고 있습니다.
-
12event Transfer(address indexed from, address indexed to, uint256 value)event Approval(address indexed owner, address indexed spender, uint256 value)
ERC20 구현
ERC20은 표준 명세로 다양한 방법으로 구현될 수 있습니다. 오픈제플린은 ERC20에 대한 참조 구현을 제시하고 있습니다. ERC20 토큰을 발행하는 개발자는 직접 표준을 구현하기 보다는 오픈제플린의 ERC20 참조 구현 컨트랙트를 상속받아 사용하는 것이 좋습니다.
오픈제플린은 ERC20 명세를 IERC20 인터페이스로 작성하고, ERC20 컨트랙트로 이 인터페이스를 구현합니다.
오픈제플린은 표준을 충실히 반영해서 IERC20에 name, symbol, decimals와 같은 선택적인 부분은 포함하고 있지 않습니다.
오픈제플린은 ERC20 구현에 다음과 같은 기능들을 추가하고 있습니다.
- 생성자
- name, symbol을 인자로 받습니다.
- decimals는 18로 설정합니다. 대부분의 토큰들이 18로 설정하고 있는 점을 반영한 것입니다.
- 18로 설정하고 싶지 않은 경우에는 이 컨트랙트를 상속받은 컨트랙트의 생성자에서 _setupDecimals로 설정합니다.
- ERC20을 확장하는 클래스에서 사용할 수 있도록 토큰 발행(mint)과 소각(burn) 기능을 internal로 작성해 두고 있습니다.
- 위임 수량을 늘리거나(increaseAllowance) 줄일 수(decreaseAllowance) 있도록 합니다.
ERC721
ERC721은 대체불가능한 토큰 표준입니다. 대체불가능하다는 것은 그 자체로 고유하다는 것입니다. 그 자체로 고유하기 때문에 나눌 수도 없습니다. 고유한 수집물이나 증서와 같은 것에 사용됩니다.
ERC721은 ERC165로 부터 확장되었습니다.
- ERC165는 Standard Interface Detection로 알려진 표준으로, 컨트랙트로 하여금 어떤 함수를 지원하는지 답하도록 합니다.
-
1function supportsInterface(bytes4 interfaceId) external view returns (bool)
- interfaceId
- 함수 시그니처의 첫 4바이트
- interfaceId
-
ERC721 토큰도 ERC20 토큰과 마찬가지로 어떤 토큰인지 알아야 합니다. 나눌 수 없으니 decimals는 필요 없습니다.
- name, symbol
- ERC721은 ERC721Metadata로 분리해서 명세합니다. ERC721Metadata을 구현하는 것은 선택적입니다.
-
12function name() public view returns (string)function symbol() public view returns (string)
-
- ERC721은 ERC721Metadata로 분리해서 명세합니다. ERC721Metadata을 구현하는 것은 선택적입니다.
- 토큰에 대한 이름, 설명, 이미지 정보를 담은 json 파일의 url
- tokenURI
-
1function tokenURI(uint256 tokenId) external view returns (string);
-
- tokenURI
ERC20과 비교해 볼 때 ERC721의 가장 큰 특징은 고유성입니다.
- 토큰 자체가 고유한 id를 가져야 합니다.
- 소유권이 개별 토큰별로 설정되어야 합니다. 소유자는 고유한 id를 갖는 토큰을 다수 보유할 수 있습니다.
- 소유자가 보유한 토큰 수량을 구할 수 있어야 합니다. id로 소유자가 누구인지 알 수 있어야 합니다.
-
12function balanceOf(address owner) external view returns (uint256);function ownerOf(uint256 tokenId) external view returns (address);
-
- 소유자가 보유한 토큰 수량을 구할 수 있어야 합니다. id로 소유자가 누구인지 알 수 있어야 합니다.
- 전송은 개별 토큰 별로 행해져야 합니다.
-
1function transferFrom(address from, address to, uint256 tokenId) external payable;
- ERC721은 ERC20의 전송과 위임 전송을 분리하지 않고 하나의 transferFrom으로 명세합니다.
- ERC721은 수신자가 컨트랙트인 경우에 안전한 처리를 할 수 있도록 safeTransferFrom을 지원합니다.
- safeTransferFrom는 수신자가 컨트랙트인 경우 토큰 전송이 성공적으로 이루어졌는지를 확인할 수 있도록 합니다. 컨트랙트에 토큰 전송 시 추가적인 데이터를 전송할 수도 있습니다.
-
12function safeTransferFrom(address from, address to, uint256 tokenId) external payable;function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) external payable;
-
- safeTransferFrom는 수신자가 컨트랙트인 경우 토큰 전송이 성공적으로 이루어졌는지를 확인할 수 있도록 합니다. 컨트랙트에 토큰 전송 시 추가적인 데이터를 전송할 수도 있습니다.
-
- 위임도 개별 토큰 별로 행해져야 합니다. 전체 토큰에 대해 위임을 설정하고 싶을 수도 있습니다. 해당 토큰에 대한 전송 위임이 누구에게 되었는지를 알고 싶을 수 있습니다. 어떤 계정이 소유자의 전체 토큰에 대해 위임 받았는지를 알고 싶을 수도 있습니다.
-
1234function 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);
-
ERC721은 전송, 위임 시에 이벤트를 발생하도록 합니다.
-
123event 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);
ERC721은 토큰을 열거하기 위한 기능들을 하나로 묶어 ERC721Enumerable로 명세합니다. ERC721Enumerable은 ERC721을 확장합니다. 선택적입니다.
- 토큰의 총 공급 수량을 알고, 인덱스로 개별 토큰을 구할 수 있으면 전체 토큰을 열거할 수 있습니다. 소유자가 보유한 토큰 수량을 알고 인덱스를 알면 소유가가 보유한 토큰을 열거할 수 있습니다.
-
123function totalSupply() external view returns (uint256);function tokenByIndex(uint256 index) external view returns (uint256);function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
- 소유자가 보유한 수량은 balanceOf를 통해 구할 수 있습니다.
- 소유자가 보유한 수량은 balanceOf를 통해 구할 수 있습니다.
-
ERC721은 컨트랙트가 safeTransferFrom의 수신자가 되는 것을 지원하기 위해 ERC721TokenReceiver를 명세합니다. safeTransferFrom의 수신자가 되려는 컨트랙트는 ERC721TokenReceiver를 구현해야 합니다.
-
1function onERC721Received(address operator, address from, uint256 tokenId, bytes data) external returns(bytes4);
ERC721 구현
오픈제플린은 IERC165, IERC721, IERC721Metadata, IERC721Enumerable 인터페이스를 제공합니다.
ERC165는 IERC165를 구현합니다. ERC721은 ERC165를 상속받고 IERC721, IERC721Metadata, IERC721Enumerable를 구현합니다. 오픈제플린의 ERC721 컨트랙트처럼 옵션들에 해당하는 인터페이스도 모두 구현한 것을 풀구현 ERC721이라고 합니다. 오픈 제플린의 ERC721 컨트랙트 소스 코드는 이번 세미나에서는 다루지는 않도록 하겠습니다.
오픈제플린은 확장된 ERC721 컨트랙트들도 구현하고 있습니다.
OpenZeppelin / openzeppelin-contracts
- Zeppelin에서 구축 및 유지보수
- 검증되고 안전한 스마트 컨트랙트 참조 구현 제공
다음과 같이 분류되어 있습니다. 오픈제플린 3.x 버전을 기준으로 설명합니다.
- access
- role-based access 기능
- cryptography
- 암호화 기능
- introspection
- 런타임시에 컨트랙트의 타입이나 속성 정보에 접근할 수 있는 기능
- 예) ERC165에서 함수의 가용 여부를 확인
- 런타임시에 컨트랙트의 타입이나 속성 정보에 접근할 수 있는 기능
- math
- 안전한 산술 계산 기능
- mocks
- 컨트랙트를 테스트하는데 사용되는 mock 컨트랙트
- payment
- 지불 관련 기능
- 예) 에스크로
- 지불 관련 기능
- token
- 토큰 표준
- utils
- 공통 유틸 기능
- GSN
- 수수료 대납 관련 기능