이더리움, DAICO 새로운 토큰 세일 모델과 구현의 주의사항.

Jason Kim
HAECHI AUDIT
Published in
16 min readMar 11, 2018

ICO 현황

(출처: http://www.ey.com/Publication/vwLUAssets/ey-research-initial-coin-offerings-icos/$File/ey-research-initial-coin-offerings-icos.pdf)

ICO(Initial Coin Offering)이란 개념이 등장한 이후로 많은 기업, 단체들이 새로운 자금 조달 방식인 ICO를 통해 자금을 유치하고 있다. 위의 EY research 의 도표에서도 볼 수 있듯 ICO로 모이는 자금은 매분기마다 기하급수적으로 증가하고있다. 현재는 이미 제품을 만들고 서비스를 운영하는 기업들까지 Reverse ICO를 하면서 신규 자금을 조달하고 있는 상황이다. 대표적인 Reverse ICO의 사례로는 텔레그램이 있고, 한국에서는 카카오에서 블록체인 회사를 설립하면서 Reverse ICO 를 하려는 것이 아니냐는 말들이 나오고 있다. ICO는 기존의 자금 조달 방식보다 상대적으로 쉽고, 작은 기업들 조차 거대한 자본을 모을 수 있는 도구가 되었다. 기존과 다르게 쉽게 자금을 모을 수 있는 환경이 생기니깐 법이 제정되기 전에 우후죽순으로 ICO를 유치하려하고 있다. 그러다보니 ICO를 통한 스캠들도 많이 생겨나게되고 제품, 서비스 개발은 커녕 모아놓은 파이를 나눠먹으려는 사내 정치 싸움등을 하는 경우도 많이 일어나고 있다. ICO에 관한 법적인 규제가 아직 미비한것도 문제가 될 수 있지만 법적인 규제 외에 기술적인 문제도 존재한다고 생각한다. 그 중에서 이더리움에서 일어나는 스마트 컨트랙트 기반 토큰 세일에 대해 이야기 해보려고한다.

대부분의 ICO들이 이더리움에서 토큰 세일 스마트 컨트랙트를 만들어서 토큰 세일을 진행한다. 토큰 이름, 발행량등을 정하고 ERC20에 맞게 토큰을 발행한다. 그러면 토큰 세일에 참여하고 싶은 투자자들이 이더리움을 스마트 컨트랙트에 전송하고 투자한 이더리움의 대가로 개발팀에서 발행하는 토큰을 지급받는다. 개발팀이 진행하는 토큰 세일이 끝나게 되면 이제 문제가 시작된다. 탈중앙화 플랫폼인 이더리움을 이용해서 자금 조달을 했다고 해서 스마트 컨트랙트에 모인 자금들이 탈중앙화된 방법으로 운영된다고 볼 수 없다. 대부분의 토큰 세일 스마트 컨트랙트는 토큰 세일이 끝나면 스마트 컨트랙트의 주인이 모인 이더리움을 마음대로 관리할 수 있게된다. 스캠 ICO들은 이렇게 모인 자금을 한번에 인출하고 도망간다. 테조스 같은 경우는 많은 금액을 모았지만 내부 경영자들의 다툼으로 인해서 한동안 제대로 개발을 진행하지도 못하였다. 하지만 그들이 모은 자금은 투자자들의 손을 이미 떠났기에 스마트 컨트랙트의 주인이 개발을 진행하지 않아도 사용하는데는 전혀 문제가 없다. 그래서 이러한 문제를 해결하기 위해 이더리움 창시자 비탈릭 부테린이 DAICO라는 개념을 도입하자고 주장했다.

DAICO 란?

(출처: https://cointelegraph.com/explained/what-is-a-daico-explained)

DAICO는 DAO(Decentralized Autonomous Organization) 와 ICO를 합친 용어이다. DAICO의 핵심은 TAP 메커니즘이다. TAP이란 “1초당 인출할 수 있는 이더리움의 양” 이다. TAP 의 양은 개발자들이 원하는 TAP의 크기를 투표로 제시하면 투자자들(토큰 홀더들)이 투표를 통해서 결정할 수 있다. 그리고 투자자들은 개발팀이 제대로 개발을 안하거나 문제가 있다고 판단하면 Refund 투표를 제기해서 스마트 컨트랙트에 남은 자금을 가지고 있는 토큰 비율만큼 환불 받을 수 있는 기능도 있다. DAICO는 기존의 ICO 스마트 컨트랙트들 보다 진화되고 민주적인 요소를 담고있다. 하지만 아직 제대로된 레퍼런스 구현체가 존재하지 않아서인지 토큰 개발팀이 DAICO에 대한 관심이 적어서인지 DAICO로 진행되는 토큰 세일은 거의 없다. 그럼에도 불구하고 ABYSS라는 게임 회사에서 최초로 DAICO를 구현한 토큰 세일 스마트 컨트랙트를 통해서 자금조달을 하려고 한다.

ABYSS 의 DAICO 스마트 컨트랙트 구조

ABYSS에서 기존의 토큰 세일 방식에서 DAICO 방식을 이용해서 토큰 세일을 진행하고 자금을 관리하는 것은 상당히 고무적인 현상이다. ABYSS 의 Github 계정에 가보면 DAICO-Smart-Contract 라는 코드가 존재한다. 이 코드를 바탕으로 ABYSS가 DAICO를 어떻게 구현했는지 살펴보려고 한다.

(출처: https://github.com/theabyssportal/DAICO-Smart-Contract)

일반적인 토큰 세일 스마트 컨트랙트들은 상당히 간단한 편이다. 하지만 DAICO 구현에서는 투표 기능이 들어가야되므로 다른 토큰 세일 스마트 컨트랙트에 비해 코드량이 많다. 스마트 컨트랙트에는 크게 두 부분이 존재한다. 첫 번째는 토큰 판매에 대한 기능들이고 두 번째는 투표에 관한 기능들이다. 우선 각각의 파일과 폴더가 어떠한 기능들을 담당하고 있는지 살펴보자.

fund: 토큰 판매에 관한 인터페이스인 ICrowdsaleFund.sol 와 투표에 관한 인터페이스인IVotingManagedFund.sol 이 존재한다.

math: 사칙연산이 원하는 의도대로 수행됬는지 검사하는 SafeMath.sol 이 존재한다. overflow 같은 일이 발생하면 생각했던 로직과 전혀다른 결과가 발생할 수 있기에 스마트 컨트랙트에서 사칙연산을 할 때는 항상 SafeMath.sol 같은 것을 사용해야 한다.

ownership: 스마트 컨트랙트의 주인을 변경하는 기능이 구현되어 있다. Solidity 스마트 컨트랙트에는 modifier 라는 개념이 있는데 onlyOwner 라는 modifier 는 함수 호출자가 지정된 owner 의 주소인지 검증하는 역할을 한다. 아래 코드와 같이 transferOwnership 에 onlyOwner 를 달아준다면 해당 함수는 owner 로 주소에서 함수를 호출할 때만 require 조건을 만족시켜서 에러가 발생하지 않고 owner 가 바뀌는 기능이 실행된다.

modifier onlyOwner() {        
require(msg.sender == owner);
_;
}
function transferOwnership(address _newOwner) public onlyOwner {
require(_newOwner != owner);
newOwner = _newOwner;
}

token: ERC20에 맞는 토큰 구현이 되어있다. 일반적으로 토큰 세일 스마트 컨트랙트를 구현하면 해당 폴더에 존재하는 Solidity 파일들이 전부인 경우가 많다. 이 부분은 특정 기간동안 토큰을 전송하지 못하게 하는 기능등을 제공하는 코드가 존재하고 중요한 부분이지만 이번 글은 DAICO의 투표 관련 기능에 대해 초점을 맞출 예정이므로 자세히 분석하진 않을 예정이다.

voting: 투표의 기본적인 기능들을 구현한 BaseVoting.sol 이 존재한다. 투표의 기간을 얼마나 할지, 투표 참여율이 얼마 이상이 되야 투표를 승인할지, 투표의 찬성/반대 결과를 비교하는 기능들이 구현되어 있다. ABYSS 의 DAICO 구현체에는 총 4가지 Voting 이 존재하는데 이중에서 토큰 홀더들이 투표하는 기능인 TapVoting, BufferVoting, RefundVoting 은 모두 BaseVoting 을 상속받고 있다.

AbyssToken.sol: token 폴더에 구현된 기능들을 상속받고 있으면 토큰 이름, 발행량등이 정의 되어있다.

CrowdSale.sol, Fund.sol: 토큰 세일과 자금 인출등에 관한 기능들이 들어있다. 이 부분 역시 DAICO 의 투표 부분에 집중하기 위해서 자세한 설명은 생략한다.

TapVoting.sol, BufferVoting.sol, RefundVoting.sol: 세 가지 투표 모두 토큰 홀더들이 투표를 하는 BaseVoting.sol 을 상속받고 있지만 투표의 목적은 각기 다르다. TapVoting.sol은 DAICO 의 TAP사이즈를 정하는 투표를 한다. BufferVoting.sol은 정의된 TAP 사이즈 외에 추가로 자금이 더 필요할 때 여유분으로 뺄 수 있는 자금을 정하는 투표를 한다. RefundVoting.sol 은 ICO에 문제가 생겼을 때 투자자들의 자금을 환불할지 결정하는 투표를 한다.

OracleVoting.sol: Oracle 로 지정된 주소들이 투표를 통해서 RefundVoting이 열릴지를 결정한다. 해당 투표는 RefundVoting 자체를 열지 정하는 투표이다. 실제로 환불이 일어날지 정하는 투표가 아니다. 환불을 할지 말지 정하는 RefundVoting을 열기 위한 투표이다. 환불의 여부는 앞에서 설명한 RefundVoting.sol 에서 결정한다. OracleVoting 참여자들은 토큰 홀더들이 아니라 미리 지정된 Oracle 들이다.

VotingManagedFund.sol: Fund.sol 을 상속 받고있고 TapVoting, BufferVoting, RefundVoting, OracleVoting 을 생성하고 각각의 결과를 받아서 자금의 분배를 결정하는 역할을 한다.

ABYSS 의 DAICO 스마트 컨트랙트 문제점

위에서 간단하게 스마트 컨트랙트가 어떠한 구조를 가지고 있는지 설명했다. 모든 코드를 설명하기 보다는 중요하다고 생각하는 부분들을 살펴보고자 한다. 실제로 TAP 메커니즘을 따라서 정해진 TAP 사이즈에 맞게 금액을 출금할 수 있는지 살펴보자.

function calcTapAmount() internal view returns(uint256) {
uint256 amount = safeMul(safeSub(now, lastWithdrawTime), tap);
if(this.balance < amount) {
amount = this.balance;
}
return amount;
}

function withdraw() public onlyOwner {
require(state == FundState.TeamWithdraw);
uint256 amount = calcTapAmount();
lastWithdrawTime = now;
teamWallet.transfer(amount);
Withdraw(amount, now);
}

위의 코드는 투표를 통해 정해진 tap 사이즈를 통해서 teamWallet으로 이더리움을 보내는 코드이다. 인출할 이더리움의 양은 출금하려는 시간과 마지막 인출시간을 뺀 다음 tap 사이즈를 곱해서 결정된다. 이를 보면 tap 사이즈에 따라 출금할 이더리움의 양이 정해진다는 것을 알 수 있다. tap 사이즈에 따라 출금가능한 이더리움의 양이 정해지므로 tap 사이즈를 정하는 투표가 공정하게 이뤄지는지가 중요하다. 다음으로는 투표의 결과가 어떻게 정해지는지 살펴보자.

function tryToFinalize() public notFinalized returns(bool) {
require(now >= endTime);
finalized = true;
onVotingFinish(isSubjectApproved());
return true;
}
function isSubjectApproved() internal view returns(bool) {
uint256 votedTokensPerc = getVotedTokensPerc();
if(votedTokensPerc >= minTokensPerc && yesCounter > noCounter) {
return true;
}
return false;
}
function getVotedTokensPerc() public view returns(uint256) {
return safeDiv(safeMul(safeAdd(yesCounter, noCounter), 100), token.totalSupply());
}

투표가 끝날때 tryToFinalize() 함수를 호출하고 isSubjectApproved() 를 통해서 현재 제기한 투표의 결과가 찬성인지 반대인지 boolean 값을 받는것을 알 수 있다. isSubjectApproved() 를 보면 투표 참여율이 minTokensPerc 이상이고 yesCounter(찬성에 투표한 사람)이 noCounter(반대에 투표한 사람)보다 높아야지 투표결과가 찬성임을 알 수 있다. getVotedTokensPerc() 에서 참여율 계산하는 공식은 전체 토큰 공급량을 분모로하고 투표 참여자를 분자로해서 계산한 값을 퍼센트로 나타낸 것이므로 1토큰당 1개의 투표권이 주어진다고 볼 수 있다. 여기서 중요한 것은 minTokensPerc 가 어떻게 정해지는지가 중요하다. 찬성표가 많다고하더라도 투표율이 너무적어서 의미없는 수치이면 투표 안건이 통과되면 안되기 때문이다.

uint256 public minVotedTokensPerc = 0;
uint256 public constant MAX_VOTED_TOKEN_PERC = 10;
function updateMinVotedTokens(uint256 _minVotedTokensPerc) internal {
if(minVotedTokensPerc >= MAX_VOTED_TOKEN_PERC) {
return;
}
uint256 newPerc = safeDiv(_minVotedTokensPerc, 2);
if(newPerc > MAX_VOTED_TOKEN_PERC) {
minVotedTokensPerc = MAX_VOTED_TOKEN_PERC;
return;
}
minVotedTokensPerc = newPerc;
}

minVotedTokensPerc 값은 초기값은 0이고 tapVoting 이나 bufferVoting 이 끝나고 갱신된다. 다시말하자면 첫 tap 사이즈를 늘리기 위한 투표는 최소 투표율이 0 이므로 투표율이 의미가 없다. 따라서 ICO 직후에 많은 양의 토큰을 가지고 있는 토큰 홀더들의 결정으로 tap 사이즈가 결정될 확률이 높다. 일반적으로 ICO이후 토큰 세일 물량을 제외하면 개발팀과 어드바이저등이 초기에 많은 분량을 가져가는데 그들의 투표 결과로 tap 사이즈가 쉽게 조절될 수 있음을 의미한다. 그리고 updateMinVotedTokens() 함수를 살펴보면 최소 투표율이 MAX_VOTED_TOKEN_PERC 인 10% 를 넘길 수 없음을 볼 수 있다. 이렇게 작은 수치의 투표율은 소수의 토큰 홀더들이 투표의 결과를 좌지우지 할 수 있다는 것을 의미한다. 개발팀이 그들이 유리한 방향으로 투표 결과를 만들 수 있을 것이다.

다음으로 DAICO 의 핵심인 Refund 기능이 제대로 구현되어 있는지 살펴보자. 우선 Refund 를 하기 위해서는 두 단계가 필요하다. 첫 단계는 OracleVoting 을 통해서 Oracle 주소로 지정된 사람들이 투표를 한다. 이 사람들이 현재 개발팀이 문제가 있다고 투표로 판단하면 RefundVoting이 열리게 된다. 두번째 단계는 앞에서 설명한 투표 방식을 통해서 RefundVoting을 토큰 홀더들이 진행하는 것이다. 최소 투표율을 넘기고 투표 찬성인원이 더 많으면 Refund 기능이 활성화된다. 이럴 경우 토큰 홀더들은 스마트 컨트랙트가 보유하고 있는 이더리움의 양을 자신이 가지고 있는 토큰 비율만큼 계산해서 되돌려 받을 수 있다. 두 번째 단계는 TapVoting, BufferVoting 과 마찬가지르 BaseVoting 을 상속받았으므로 투표 과정이 동일하다. 다만 OracleVoting 은 다른 방법으로 이뤄지는데 이 부분을 살펴보자.

function isSubjectApproved() internal view returns(bool){
if(safeSub(yesCounter, noCounter) > 0 && safeAdd(yesCounter, noCounter) >= minTotalVoted) {
return true;
}
return false;
}

OracleVoting 에서 찬성, 반대는 isSubjectApproved() 로 결정된다. TapVoting 과는 다르게 최소 투표율을 계산하는 것이 아니라 minTotalVoted 가 넘고 찬성표가 더 많으면 투표가 승인이 된다. 그렇다면 minTotalVoted 는 어떻게 결정되고 투표에 참여할 수 있는 Oracle 주소는 어떻게 정해지는 살펴보자.

function setOracles(address[] _oraclesAddresses, uint8 _minOraclesVoted) public onlyOwner {
require(oraclesAddresses.length == 0);
oraclesAddresses = _oraclesAddresses;
for(uint256 i = 0; i < oraclesAddresses.length; i++) {
oracles[oraclesAddresses[i]] = true;
}
minOraclesVoted = _minOraclesVoted;
}

위의 코드를 통해서 OracleVoting 에 참여할 수 있는 Oracle 주소들과 최소 투표수가 정해진다. 하지만 이 함수를 호출할 수 있는 사람은 onlyOwner 로 스마트 컨트랙트의 주인이다. 즉 ICO 를 주최하는 개발팀이 Oracle 주소를 마음대로 변경할 수 있다. 따라서 개발팀이 개발을 진행하지 않거나 스캠이더라도 Oracle 주소를 자신들이 유리한 주소로 변경해서 RefundVoting 자체가 열리지 않게 막을 수 있다.

글을 마치며

무분별하고 중앙 집중화된 ICO 를 막아보기위해 DAICO 라는 개념이 도입된 것은 상당히 고무적이다. 하지만 아쉽게도 DAICO 첫 레퍼런스인 ABYSS 의 스마트 컨트랙트는 아쉬운 부분이 많아보인다. 민주적인 과정을 통해서 자금을 관리 할 수 있을거 같지만 실제로는 다른 토큰 세일 스마트 컨트랙트와 마찬가지로 중앙집중화된 부분들이 많아보인다. 보다 더 민주적인 DAICO 구현체들이 나와서 단순히 DAICO 방식으로 자금 조달을 한다는게 마케팅 용어로만 쓰이지 않기를 바란다.

--

--

Jason Kim
HAECHI AUDIT

Haechi Labs CEO, Haechi Labs provides Henesis & Haechi Audit.