Monthly Archives: September 2018

스마트컨트랙 폰지사기

요 근래 많은 사용자 수가 유입되고 있는 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 정도 나가고 있다.

 

도박 수수료 1%

블록체인 도박들중 단순한 형태인 주사위 던지기나 이더롤 같은 도박들은 하우스와 고객간 이길 확율은 동일하게 하고 한번 돈을 걸때 마다 1%씩 하우스 피를 받는다. 1%는 상당히 적어 보이지만 상당한 비용이다.

100원으로 이러한 도박을 계속 한다고 가정해보자. 실제 결과가 아닌 단순 기대치 계산을 해보면

첫판에 100원을 걸면 하우스가 1원을 떼가고 99원이 남는다.(하우스대 고객 승률은 동일하므로 기대치는 하우스 피을 뺀 나머지 전체이다) 다시 99원을 걸면 하우스는 0.99원을 떼간다. 즉 하우스는 배팅액의 총액에서 1%을 떼가게 된다. 베팅 총액을 구해보자.

100 + 100 * 0.99 + 100 * 0.99 * 0.99 + 100 * 0.99^3 …

등비수열이다.

형식의 등비수열의 합은

이므로 이경우 a = 100, r = 0.99 이다

100판을 한다고 하면

100(1-0.99^100)/(1-0.99) = 6339.67658727

이 되므로 배팅 총액은 6340원이고 이때 하우스는 이것의 1%인 63원을 가져가게 되고 도박꾼은 수중에 34원만 남게된다. 1000판을 해보면?

100(1-0.99^1000)/(1-0.99) = 9999.56828753

이고 이경우 하우스가 99.99원을 가져가고 고객은 0.01원 이하의 돈이 남게 된다. 즉 오링이다. 시뮬레이션을 해보면 남은 돈을 다거는 전략으로갈때 100원으로 시작해서 1원이 남게 되는데는 460판 정도 걸린다. 한판에 1분이라고 하면 460분이고 7시간 반 정도 재미있게 놀수 있다. 그렇다면 7시간을 노는데 얼마를 지불하는 것이 적당할까? 그 값이 바로 본인이 게임을 시작할때 투입하기로 결정하는 값이다. 일반 카지노에서도 바카라, 블랙잭 같은 게임은 하우스 승률이 1%정도 높으므로 비슷하게 적용할 수 있을 것이다.

Smallet Connector

대부분의 이더리움 Dapp들은 메타마스크를 표준처럼 사용하고 있다. 즉 결제를 하고 그 결과를 기다리고 하는 작업들을 일반 지갑으로 할 경우 편리성이 떨어져서 실제 운영이 어렵기 때문이다. Dapp을 사용하지 못한다면 이더리움을 구해서 고작 거래소에서 사고 팔고 하는거 말고는 할 것이 별로 없다. 그러니 지갑은 필수적으로 DApp을 지원해야 한다.

그래서 이더리움 스몰렛에서 메타마스크와 호환되는 결제 기능을 만들어 보기로 하였다. 메타마스크는 web3라는 개체를 웹페이지에 삽입하는 방법을 사용한다. 웹 프로그램은 이 개체가 있으면 메타마스크가 있다고 보고 web3에 붙은 API들을 사용하는 것이다.

메타마스크 소스에서 대부분의 UI를 다 없애고 실제 결제 부분을 안드로이드 앱으로 통신하여 앱에서 실제 결제 행위가 이루어지 지도록 하는 것이 구현의 핵심이다.

만들어진 크롬 확장프로그램은 여기에서 설치하면 된다. 스몰렛 커넥트라고 이름 지었다.

설치가 되면 아이콘을 눌러서 팝업을 연다.

   

Connet to Smallet 버튼을 누르면 그림과 같이 QR 코드가 나오는데 이더리움 스몰렛 앱에서 메뉴>지갑접속을 해서 이 QR 코드를 읽는다. 접속이 되면 아래 화면과 같이 계좌번호, 디바이스 이름 등이 나타난다.

모든 준비가 되었다. 이제 Dapp을 실행하여 실전을 해볼때다. 현재 어느정도 활성화된 Dapp은 거래소, 도박, 게임 정도이다. Dapp 종류들은 https://dappradar.com/ 에서 찾아 볼 수 있다. 사용자수나 거래 대금등으로 순위를 매겨서 볼 수 있다. 아직 거래소나 도박 외에는 별로 활성화된 DApp이 없음을 알 수 있다.

https://www.blockasino.com/ 에서 바카라 비슷한 게임을 해보자.

주사위 3개를 던져서 합계를 가지고 결정되는 도박이다. 그림과 같이 원하는 판에다 이더를 걸면된다. 중앙의 3개가 같은 숫자가 나오는 부분에 걸지 않는다면 세개가 같은 숫자가 나오면 하우스가 먹는 방식이다. 그림은 SMALL에다 0.03 이더를 베팅한 경우이다. SMALL은 세개의 주사위 합이 4~10 사이로 나오면 두배를 먹게 된다. 3개가 같은 숫자가 나오는 경우를 제외하면 기대값은 100%이다.

“PLACE BET!” 버튼을 누르면 이더리움 스몰렛 앱에 결제 요청이 오면 모든 실행이 제대로 되고 있는 것이다. 결제를 해주면 그 다음은 알아서 돌아간다. 블록체인에 기록되는데 시간이 걸리므로 한참 기다려야한다. 오래 기다려도 안되면 etherscan.io에서 본인 계정을 확인해 보면 진행상황을 알 수 있다.

이더를 소비하지 않고 테스트를 해보는 방법은 메시지 사인을 보내 보는 것이다. https://smallet.co/contracts2/#/sign-message 에서 테스트로 해 볼 수 있다.

모바일 브라우저에서도 가능한데, 크롬은 모바일 버전에서 확장프로그램을 지원하지 않으므로 크롬 확장 프로그램을 지원하는 Yandex 브라우저를 설치해서 해 볼 수 있다.

도박이니 만큼 절대 많은 돈을 걸지 않도록 주의 한다. 특히 하우스가 떼가는 수수료가 적으것 같지만 반복적으로 베팅할 경우 상당히 큰 금액이라 장기적으로 플레이 하면 당연히 돈을 다 잃게 되어 있다. 컨트랙트에 의해 움직이는 도박은 반칙은 없다. 즉 하우스가 나를 속이지는 않지만 컨트랙트 자체의 룰이 장기적으로 플레이어가 돈을 다 잃는 구조로 되어 있다는 점을 잊어서는 안된다. 거의 모든 가상화폐 도박장이 동일하다. 많은 이더리움 도박장들이 1% 정도의 수수료가 싸다고 하지만 주식이나 가상화폐 거래 수수료가 불과 0.0x % 대인것을 감안하면 매우 비싼 것이다.