이더리움에서 토큰이란 이더리움 애플리케이션, ÐApp의 자산을 말한다. ICO를 통해 프로젝트 수행에 필요한 자금 유치와 그에 따른 이익분배 목적으로 토큰을 판매할 수도 있고(증권형 토큰, security token), 특정 토큰을 가져야만 ÐApp을 사용하게 할 수도 있다(utility token, work token). 결국 토큰의 가치는 ÐApp의 가치라고 볼 수 있다.
이더리움에는 이미 ETH가 있지만 일상에서도 화폐만으로 물건을 사거나 서비스를 이용할 수 있는 것은 아니라는 점을 생각해보면 토큰의 기능을 이해할 수 있을 것 같다.
“토큰”이라는 용어를 “코인”과 구별해서 쓰지만 교환을 위한 매개수단이라는 의미에서, 각 블록체인마다 고유한 토큰이 있다고 말할 수도 있다(비트코인은 BTC, 이더리움은 ETH, 이오스의 EOS). 비탈릭 부테린이 ETH를 다음과 같이 설명한 것처럼 말이다.
If the account is an EOA, the state simply stores the account’s balance in ether (Ethereum’s internal crypto-token, similar to bitcoin or XRP in function) and a sequence number used to prevent transaction replay attacks. If the account is a contract, the state stores the contract’s code, as well as the contract’s storage, a key-value database.
ERC-20
ERC-20은 “standard interface for tokens”에 관한 표준으로, 토큰에서 기본적으로 구현해야 할 “인터페이스”를 기술하고 있다. ERC는 Ethereum Request for Comment의 약자로 RFC처럼 기술 표준을 정의한 문서들이라고 보면 되겠다.
ERC-20 토큰을 구현한 예제들 중 가장 많이 활용되는 것이 OpenZeppelin에서 만든 구현체가 아닐까 싶다. 이 글에서는 이 소스를 가지고 FOO라는 토큰을 만들어 보기로 한다. 기본적인 토큰 컨트랙트와 함께 간단한 토큰 전송 화면도 함께 구현해보도록 하겠다(이 예제는 Truffle box의 TutorialToken을 참고하여 구성했다).
우선 기본 클래스로 사용할 ERC20Basic.sol을 살펴보도록 하자.
이것은 ERC-20 표준의 기본 인터페이스에 해당한다. 여기에 없는 나머지 메소드들은 선택 사항이거나 ERC20.sol에 분리되어 있다. 따라서 ERC-20에서 정의한 모든 것을 구현하려면 ERC20.sol을 이용하거나 StandardToken.sol을 사용하면 되겠다. 이 글에서는 아래 기능은 필요없으므로 ERC20Basic.sol을 사용할 것이다
(이 글이 작성된 후 OpenZeppelin의 소스 파일이 바뀌었기 때문에 파일 위차나 구조가 다를 수 있다).
각 메소드들이 어떤 기능을 해야 하는지 알아보자.
name
OPTIONAL. 토큰의 이름을 리턴한다.
symbol
OPTIONAL. 토큰 표시를 리턴한다. 여기서는 FOO가 될 것이다.
decimals
OPTIONAL. 토큰이 소수점 몇 째자리까지 내려갈 수 있는지 리턴한다. 2라면 0.01까지 사용할 수 있다.
totalSupply
토큰의 총 공급량을 리턴한다. 유통가능량이라고 보면 된다(공급된 토큰을 “소각”시킬 수 있으므로 없어지면 유통량도 줄어든다).
balanceOf
계정주소 _owner가 소유한 토큰 개수를 리턴한다.
transfer
_value 만큼의 토큰을 _to 주소로 보낸다. 전송 후 Transfer 이벤트를 발생시켜야 한다. _from 주소에 보내는 양만큼의 토큰이 없으면 예외가 발생한다. 보내는 토큰이 0이라도 정상 처리해야 한다.
Transfer
transfer 실행 후 발생시키는 이벤트이다. 토큰이 새로 생성될 때는 _from 주소에 0x00으로 하여 이벤트를 발생시킨다.
다음 메소드는 구현하지 않을 것이다.
transferFrom
_from 주소에서 _to 주소로 _value 만큼의 토큰을 보낸다. Transfer 이벤트를 발생시킨다. transfer와는 달리 대리 송금 기능을 위한 것이다. 이 메소드를 호출하는 계정은 (당연히) 토큰 소유 계정으로 부터 대리 송금에 대한 승인(approve)이 있어야 하고 없는 경우 예외를 발생시켜야 한다.
approve
_spender 가 _value 해당하는 토큰 이내에서 대리 송금할 수 있도록 승인한다. 만약에 기존에 승인된 정보가 있으면 overwrite한다.
allowance
대리 송금자가 송금할 수 있는 토큰의 개수를 리턴한다.
Approval
approve가 호출되면 이벤트를 발생시킨다.
토큰 컨트랙트
이제 토큰 컨트랙트를 작성한다. 앞서 기술한 메소드를 스펙에 맞게 구현하면 된다. 이번에는 윈도우 환경에서 Truffle과 Ganache GUI를 사용할 것이다.
우선 프로젝트 폴더 FooToken을 생성하고 truffle init 으로 기본 폴더들을 생성한다.
contracts 폴더에 다음과 같이 소스 파일 FooToken.sol을 작성한다.
OpenZeppelin의 소스를 활용하기 위해서는 보통 레포지토리의 전체를 내려받지만 여기서는 필요한 파일 ERC20Basic.sol과 SafeMath.sol 두 개만 사용한다. OpenZeppelin에서 제공하는 솔리디티 파일을 모두 설치하려면 다음과 같이 하면 된다.
솔리디티는 자바의 상속 처럼 컨트랙트를 상속받을 수 있다. ERC20Basic.sol은 구현된 부분이 없으므로 인터페이스처럼 보이지만 인터페이스는 모든 메소드를 구현해야 한다.
SafeMath.sol 은 라이브러리인데 using A for B 구문은 타입 B에 대해서만 라이브러리 A를 사용하겠다는 의미이다.
이 라이브러리는 연산시 발생하는 오버플로를 방지하기 위한 것이므로 사용하는 것이 좋다.
예를 들어 SafeMath.sol 에 있는 add 라는 메소드의 사용이 가능하다. 다음과 같이 두 가지 형태로 사용이 가능하다.
totalSupply는 10억개로 정했다. 상태 변수를 public으로 선언하면 getter에 해당하는 메소드를 자동으로 생성한다. decimals = 2 이므로 0.01 FOO의 사용이 가능하다.
중요한 것은 토큰 장부에 해당하는 mapping(address => uint256) balances 이다. 솔리디티의 레퍼런스 타입으로 mapping 이라는 것이 있는데 key-value의 테이블 구조와 유사하다고 생각하면 되겠다. 의미는 다음 그림과 같다.
mapping 타입을 선언할 때 key의 이름을 정하는 것이 아니라 타입을 정한다. address 타입이 key가 되고 부호없는 정수 unit256이 value가 되는 것이다. 이 자료구조에 어떤 계정 주소가 몇 개의 FOO 토큰을 소유하고 있는지 저장한다. 따라서 balanceOf메소드는 다음과 같이 구현될 것이다.
transfer 메소드는 토큰 잔액이 송금하려는 토큰의 개수보다는 커야 하므로 require라는 예외 발생 구문이 들어간다. 조건이 false이면 예외를 발생시킨다. 예외 메시지는 개발자가 알아보기 위한 것이다. 송금 후에 토큰의 잔액을 가감해주는 것은 SafeMath를 사용하고 ERC-20 스펙에 의해 Transfer이벤트를 발생시킨다.
이제 컴파일을 해보자.
결과 디렉토리에 가보면 다음과 같은 json 파일들이 만들어진다.
배포 스크립트는 다음과 같이 작성하면 된다. FooToken.sol에 해당하는 것만 배포하면 된다.
truffle.js 설정 파일은 다음과 같다. 로컬 가상 이더리움에 해당하는 Ganache에 배포할 것이므로 Ganache의 디폴트 값으로 맞춘다.
배포를 시작한다.
Ganache에서도 트랜잭션 조회가 가능하다.
에러 없이 배포되었다면 truffle console에서 간단히 확인한다. 생성자에서 토큰이 처음 생성되면 발행된 토큰 10억개는 컨트랙트를 배포한 계정 소유가 되도록 했다(토큰 발행 만큼은 centralization이다).