요 근래 많은 사용자 수가 유입되고 있는 333ETH의 코드를 분석해 보자. 많은 폰지 사기들이 있어 왔지만 이렇게 코드로 확실히 그 모습을 드러내는 경우는 많지 않고 대부분 투자자를 모으지 못하고 끝나는데 333ETH는 출시된지 20일정도된 지금 상당한 자금을 모으고 있다.
컨트랙트 주소는 0x311f71389e3de68f7b2097ad02c6ad7b2dde4c71이다. 이 주소로 0.01 ETH 이상을 보내면 투자는 완료된다. 배당은 이 주소로 0ETH를 전송하면 보내준다. 아래 코드에 나와 있다.
function() public payable { // investor get him dividends if (msg.value == 0) { getMyDividends(); return; } // sender do invest address a = msg.data.toAddr(); address[3] memory refs; if (a.notZero()) { refs[0] = a; doInvest(refs); } else { doInvest(refs); } }
우선 doInvest를 살펴보자.
function doInvest(address[3] refs) public payable notOnPause balanceChanged { require(msg.value >= minInvesment, "msg.value must be >= minInvesment"); require(address(this).balance <= maxBalance, "the contract eth balance limit"); uint value = msg.value; // ref system works only once for sender-referral if (!m_referrals[msg.sender]) { // level 1 if (notZeroNotSender(refs[0]) && m_investors.contains(refs[0])) { uint reward = m_refPercent.mul(value); assert(m_investors.addRefBonus(refs[0], reward)); // referrer 1 bonus m_referrals[msg.sender] = true; value = m_dividendsPercent.add(value); // referral bonus emit LogNewReferral(msg.sender, now, value); // level 2 if (notZeroNotSender(refs[1]) && m_investors.contains(refs[1]) && refs[0] != refs[1]) { assert(m_investors.addRefBonus(refs[1], reward)); // referrer 2 bonus // level 3 if (notZeroNotSender(refs[2]) && m_investors.contains(refs[2]) && refs[0] != refs[2] && refs[1] != refs[2]) { assert(m_investors.addRefBonus(refs[2], reward)); // referrer 3 bonus } } } } // commission adminAddr.transfer(m_adminPercent.mul(msg.value)); payerAddr.transfer(m_payerPercent.mul(msg.value)); // write to investors storage if (m_investors.contains(msg.sender)) { assert(m_investors.addValue(msg.sender, value)); } else { assert(m_investors.insert(msg.sender, value)); emit LogNewInvestor(msg.sender, now, value); } if (m_paysys.mode == Paymode.Pull) assert(m_investors.setPaymentTime(msg.sender, now)); emit LogNewInvesment(msg.sender, now, value); investmentsNum++; }
앞부분은 referrall에 관한 부분으로 중요하지 않다.(추천인에게 일정 배당을 주는 것) 489, 490 라인에서 관리자가 자기꺼 먼저 챙긴다. 즉 투자자는 24시간이 지난 다음부터 0ETH를 contract로 전송해야 일 3.33%를 받지만 관리자는 투자즉시 17%를 빼간다. Admin이 10%, payer가 7%를 떼간다.
아무튼 이런식으로 돈을 떼가면 금방 자금이 바닥나게 되어 있는데 자금이 바닥날 경우를 살펴보자.
function getMyDividends() public notOnPause atPaymode(Paymode.Pull) balanceChanged { // check investor info InvestorsStorage.investor memory investor = getMemInvestor(msg.sender); require(investor.keyIndex > 0, "sender is not investor"); if (investor.paymentTime < m_paysys.latestTime) { assert(m_investors.setPaymentTime(msg.sender, m_paysys.latestTime)); investor.paymentTime = m_paysys.latestTime; } // calculate days after latest payment uint256 daysAfter = now.sub(investor.paymentTime).div(24 hours); require(daysAfter > 0, "the latest payment was earlier than 24 hours"); assert(m_investors.setPaymentTime(msg.sender, now)); // check enough eth uint value = m_dividendsPercent.mul(investor.value) * daysAfter; if (address(this).balance < value + investor.refBonus) { nextWave(); return; } // send dividends and ref bonus if (investor.refBonus > 0) { assert(m_investors.setRefBonus(msg.sender, 0)); sendDividendsWithRefBonus(msg.sender, value, investor.refBonus); } else { sendDividends(msg.sender, value); } }
450라인에 나와있다. 더이상 줄돈이 없게 되면 nextWave를 부른다.
function nextWave() private { m_investors = new InvestorsStorage(); changePaymode(Paymode.Push); m_paysys.latestKeyIndex = m_investors.iterStart(); investmentsNum = 0; waveStartup = now; m_nextWave = false; emit LogNextWave(now); }
이 코드는 컨트랙트 마지막에 있다. 단순하다. 그냥 모든것을 리셋하고 다시 시작한다. 그동안 투자한 사람들은? 그동안 배당받은게 전부이고 다 날린거다.
이 컨트랙트는 나름 의미가 있다. 어느정도 성공하고 있기 때문에 폰지사기가 어떻게 끝나는지 분명한 수치로 아마도 영구히 블록체인에 기록되어 후세에 좋은 연구자료와 귀감이 될 것이다. 현재는 하루 1,000이더 정도 들어오고 500 정도 나가고 있다.