ICO 토큰 판매 스마트 컨트랙트 파헤쳐보기

Kyungjeon Kim
RayonProtocol
Published in
11 min readJul 13, 2018

ICO 프로젝트에 있어서 토큰 발행, 판매, 배분은 필수적이다. 많은 프로젝트가 토큰을 이더리움의 ERC20 규격 기반으로 발행하고 있다. 이 정도 내용은 ICO에 관심있거나 참여해본 사람에게는 너무 당연하고 익숙할 것이다.

그런데, ERC20 규격을 기반으로 토큰을 발행한다는 것이 무슨 의미인지, 그 규격대로 토큰만 발행하면 ICO가 되는 것인지, 그렇지 않다면 어떤 것들을 추가로 고려해야 하고 개발해야 하는지 등과 관련하여, 스마트 컨트랙트 코드 레벨까지 deep dive 해보고자 한다.

이 글은 잘 알려진 토큰 판매 스마트 컨트랙트 플랫폼인 OpenZeppelinTokenMarketNet의 코드를 활용하여 작성하였다.

Copyright 2017 TokenMarket Ltd., Gibraltar, https://tokenmarket.net

Copyright © 2016 Smart Contract Solutions, Inc.

ICO 시 스마트 컨트랙트 관련한 고려사항들

ICO 프로젝트는 보통 토큰 발행, 토큰 전송, 판매, 배포 등과 관련하여 다양한 기능, 규칙을 제시하게 된다. 일반적인 고려사항 및 몇가지 특별한 고려사항을 나열해보면 다음과 같다.

토큰

  • 토큰 기본 정보: 심볼, 이름, 소수점이하 자리수, 최대 발행량
  • 토큰 전송: 소유자가 직접 전송, 소유자가 허용한 계정에서 전송, 제3의 agent가 전송
  • 비상 정지: 해킹 등에 대비하여 관리자 이외에 전부 혹은 선택된 계정의 토큰 전송 정지 및 해제
  • 토큰 락인: 계정 별로 정해진 시점까지 토큰 전송 중지
  • 토큰 소각: 일부 토큰 소각으로 인한 유통 토큰 감소
  • 토큰 업그레이드: 다른 새로운 토큰 제공해주고 기존 토큰 소각

크라우드세일

  • 이더 수취: ICO 참여자로부터 이더 수취
  • 이더 락인: ICO 성공 전까지 이더 인출 방지
  • 한도, 시간 제한: 총 판매 개수 혹은 참여자별 구매 개수 제한, ICO 참여 시간 제한
  • 판매할 토큰 조달: 크라우드 세일이 보유한 토큰 제공 혹은 세일 즉시 발행하여 제공
  • Private/Pre Sale: 스마트 컨트랙트 배포 전 참여자에게 토큰 제공
  • 세일 참여 승인(KYC): 신원 인증 등을 거친 참여자만 세일 참여 허용
  • 가격 정책: 세일 단계, 참여 시각 등에 따른 차등 가격 적용
  • 토큰 제공 시점: 세일 참여 시점에 제공, 세일 종료 후 일괄 제공, 세일 종료 후 참여자가 인출 요청 시 제공
  • 환불: 최소 판매 수량 미달 등으로 ICO 취소되는 경우 환불

ICO 스마트 컨트랙트 구현 범위

ICO를 하기 위해 필수적인 사항만 우선 생각해보자.

토큰 발행은 필수적이다. 여기서는 ERC20 규격으로 토큰을 발행하도록 한다. ICO 참여자로부터 받을 Ether는 스마트 컨트랙트 없이 이더리움 계정을 만들어서 받으면 된다. 나머지는 과정은 스마트 컨트랙트와 무관하게, 수동으로 계산 및 처리하고, 참여자 별로 계산된 양만큼 토큰을 전송해주면 된다.

즉, 크라우드세일 스마트 컨트랙트는 선택사항이다. 하지만, 규칙과 기록을 블록체인에 투명하게 공개하겠다는 보편적인 ICO 프로젝트의 원칙 상, 토큰 판매 및 분배를 스마트 컨트랙트를 이용하여 공개적이고 자동으로 처리하는 것이 일반적이다. 어디까지 공개하고, 제한하고, 기록할 것인지는 각 프로젝트의 상황에 따라 정하면 된다.

토큰 스마트 컨트랙트 기본 기능

ERC20Basic.sol은 다음과 같이 규격만 정의되어 있다. 이 규격을 구현하고 name, symbol, decimals 멤버만 포함시키면 ERC20 토큰이 된다.

다음 규격처럼 토큰 총 발행량, 계정 별 잔고, 토큰 전송 기능을 구현하면 된다.

ERC20Basic 인터페이스

다음은 ERC20Basic을 구현한 contract이다. balances가 주소 별 잔고를 유지한다. 토큰 전송 시 잔고를 변경하는 것을 볼 수 있다. transfer 함수에 토큰 소유량을 확인하는 modifier나 require 문이 없는 것을 볼 수 있는데, 잔고 차감 시 예외가 발생하므로 보유 여부가 인증되기 때문에 문제 없다.

ERC20Basic 구현

ERC20의 부가기능으로, 다른 계정(spender)가 내 토큰의 일부를 전송할 수 있게 허용해줄 수 있다. Spender는 transferFrom 메소드를 통해 다른 계정의 토큰을 전송할 수 있다.

ERC20 인터페이스

ERC20 규격을 구현한 StandardToken은 BasicToken에서 파생되었다.

allowed는 토큰 전송 위임에 대해, 위임자, 위임받은자, 허용량을 저장한다. transferFrom 메소드는 allowed의 값에 따라 전송 허용 여부를 판단한다.

ERC20 구현

이제 StandardToken을 이용하여 총량이 고정적인 토큰을 발행해보자. symbol, name, decimals, 총 발행량을 정하고, 총 발행량 모두를 스마트 컨트랙트 소유자에게 할당하면 된다. 이제 이 소유자가 다른 계정으로 이 토큰을 전송할 수 있게 된다.

토큰 발행

ERC20 토큰의 본질

살펴본 것처럼 ERC20 토큰은 이더리움 블록체인 입장에서는 스마트 컨트랙 내에 기록된 값일 뿐이다. 다만 이 규격을 이용하면 MetaMask나 MyEtherWallet 등 3rd-party 월렛 프로그램에서 토큰 수량을 확인하고 전송할 수 있고, 거래소도 연동을 쉽게 할 수 있을 것이다.

토큰 스마트 컨트랙트 부가 기능

토큰 추가 발행

mint 메소드를 실행시키면 토큰을 추가로 발행하여 _to에게 제공한다.

비상 정지

해킹 등이 발생하여 토큰 전송 등의 기능을 일시 정지하고 싶을 수 있다. 이런 기능은 토큰 발행시 미리 포함되어야 한다.

다음 코드에서 Pausable은 pause, unpause 할수 있게 해주고 whenNotPaused modifier를 제공한다. PausableToken은 whenNotPaused 인 경우만 실행되게 되어 있다.

지정된 계정에 대해서만 기능을 막는 기능과, 일시정지된 상태에도 contract 소유주는 해당 기능을 수행 가능하도록 하는 기능을 추가로 생각해볼 수 있다.

토큰 락인

토큰 contract 자체가 락인 기능을 제공할 수 있을 것이다.

OpenZeppelin은 다음과 같이 토큰 contract 밖에 1회용 금고를 지원한다. 다음 코드에서 생성자에서 token, beneficiary, releaseTime을 설정하고 변경을 지원하지 않으니, 이 금고는 releaseTime 이후에 beneficiary가 이 contract에 포함된 token을 찾아갈 수 있다.

이 contract 주소에 토큰을 보내면 이 contract이 해당 토큰을 가지게 된다. 이것은 token contract에 기록되는 사항이므로 TokenTimelock contract에는 토큰 수신 코드가 필요 없다.

토큰 소각

다음과 같이, burner의 토큰 수와 총 발행량을 줄이기만 하면 된다.

토큰 업그레이드

향후 어떤 이유든 기존 토큰을 소각하고 다른 토큰으로 교체해주게 될지도 모른다. 이를 대비해서 토큰 contract에 UpgradeAgent를 설정할 수 있게 해주고, 토큰 소유자가 업그레이드를 요청하면, UpgradeAgent의 upgrade 등 지정된 메소드를 실행시키고, 기존 토큰은 소각하도록 구현할 수 있다.

미리 이렇게 방안을 마련하지 않더라도, 토큰 소유자가 주소 0으로 토큰을 보내면, 이를 확인해서 새로운 토큰을 전송해주는 식으로도 업그레이드를 할 수도 있다.

크라우드세일 스마트 컨트랙트

판매 가격, Ether 수신 주소, 토큰 contract를 입력으로 Crowdsale contract를 생성한다. 다음 contract의 인증, 토큰 제공 등 기능은 확장해서 구현할 수 있다.

이더 수취

크라우드세일 contract 주소로 Ether를 보내면 ‘function () external payable’ fallback 메소드가 실행된다. payable이 붙었으니 Ether를 받을 수 있는 것이다.

특정 메소드를 호출하지 않고 Ether를 보낼 수 있으니, 세일 참여자 입장에서는 간편하지만, 거래소에서 보낸 경우에도 전송이 되는데, 이 경우 참여자가 해당 계정의 비밀키를 갖고 있지 않아서 토큰을 받아서 사용할 수가 없는 문제가 있다. 이런 문제를 방지하려면 fallback 메소드가 throw 하도록 하면 된다.

function() payable {  throw;}

buyTokens 메소드에서 _fowardFund를 호출하는데, 이 메소드는 wallet에 받은 Ether를 전송한다.

이더 락인

앞에서 수신한 Ether가 wallet으로 보내지는데, 이 wallet을 다음과 같은 기능을 하는 금고 contract 주소로 설정하면 된다. 또한 크라우드세일 contract도 관련 기능을 포함해야 한다.

  • 금고 contract이 크라우드세일 contract 주소를 포함한다
  • 금고의 unlock 메소드가 호출되면, contract 소유자가 포함된 Ether를 꺼낼 수 있다. 이 메소드는 지정된 크라우드세일 contract만 호출할 수 있도록 한다.
  • 금고의 refund 메소드가 호출되면, 크라우드세일 contract에 Ether를 전량 전송한다. 이 메소드는 지정된 크라우드세일 contract만 호출할 수 있도록 한다.
  • 크라우드세일 contract는 조건에 따라 세일 성공/실패를 판단할 수 있고, 각 경우에 따라 금고 contract의 unlock 혹은 refund 메소드를 실행한다.
  • 실패시에 크라우드세일 contract는 세일 참여자의 환불 요청을 받아 Ether를 환불해준다.
  • 금고 및 크라우드세일 contract에서 소유자가 임의로 Ether를 전송하는 기능을 포함하지 않도록 한다.

한도, 시간 제한

크라우드세일의 buyTokens 함수 내에서 현재 시간, 판매된 토큰 수 등에 따라 판매할 수 없는 경우 exception을 발생하도록 하면 된다.

판매할 토큰 조달

크라우드세일 contract에 이미 발행된 토큰을 전송하거나, 토큰 전송을 허용할 수 있다. 혹은 세일이 발생할 때마다 신규 발행하여 전송할 수 있다.

다음 코드에서 buyTokens 시에, 허용된 토큰이 충분히 남았는지 검사하는 것과, 실제 토큰 전송 시 transferFrom을 통해 허용된 토큰을 전송하는 것을 볼 수 있다.

다음 코드에서는 토큰 전송 시점에 새로 토큰을 발행하고 있다.

Private/Pre Sale

크라우드세일 스마트 컨트랙트 배포 전 참여자의 참여 및 토큰 배분을 스마트 컨트랙트를 통해 처리할 수 있다. 이미 참여했으니 참여 기록을 크라우드세일 소유자가 남겨야 할 것이고, 참여자별 차등 가격을 적용해야 할 수 있다.

Public 판매 개시 전에 preallocate를 호출하여 선 판매된 토큰만큼 판매 수량을 줄여야 한다.

크라우드세일 contract 생성 전에 PreICO contract을 만들어서 Ether를 받고, 크라우드세일 contract를 나중에 세팅하고, 보유한 Ether를 크라우드세일로 전송하고 토큰을 받는 방법도 고려해볼 수 있다.

세일 참여 승인(KYC) 확인

신원 인증 등을 거친 참여자만 세일 참여를 허용하려면, 간단하게는 크라우드세일에 허용된 참여자의 주소를 계속 추가하는 방법을 생각해볼 수 있다. 이 경우 크라우드세일 소유자가 gas를 계속 사용해야하고, KYC 승인 시 빠른 시간 내에 다음의 addToWhitelist 혹은 addManyToWhitelist 메소드를 호출해야 할 수 있다. isWhitelisted modifier를 통과해야 세일에 참여가 된다.

대안으로, KYC 시스템이 KYC 기록을 서명해주고, 크라우드세일 contract에서 서명을 확인하는 방법이 있다.

KYC 시에, 인증된 주소 등 데이터를 묶어서 signerAddress에 대응되는 비밀키로 서명하고 참여자에게 제공하면, 참여자는 buyWithKYCData를 호출하면서 서명 데이터를 첨부하게되고, 이 함수에서는 데이터의 서명 주소가 signerAddress인 경우 세일 참여를 허용한다.

이 방법을 활용하면 크라우드세일 소유자가 gas를 사용할 필요가 없다.

토큰 제공 시점

세일 참여자가 이더를 전송하는 시점에 즉시 제공할 수 있고, 판매 종료 시 일괄 제공할 수도 있다. 또는 판매 종료 시에 참여자가 크라우드세일 메소드를 호출해야 받도록 할 수도 있다.

세일 soft cap이 설정되어 있고, 미달 시 환불을 해주는 경우 이더 수신 시점에 토큰을 제공하는 것은 문제가 될 수 있다.

크라우드세일이 완료 상태가 되었을 때, contract 소유자가 gas를 태워서 토큰을 일괄 전송해주는 것이 바람직해보인다.

환불

환불 상태가 되면 수신한 Ether를 수동 혹은 자동으로 크라우드세일 contract로 보내야 한다. 이후, 참여자가 refund를 호출하면 제공한 Ether 수량만큼 돌려받게 된다.

--

--