Don't think! Just do it!

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

Smart contract/Ethernaut 문제풀이

Ethernaut 문제풀이 #1 - Fallback

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

2022.02.28 - [Smart contract/Ethernaut 문제풀이] - Ethernaut 문제풀이 #0 - Hello Ethernaut

Ethernaut 두번째 Fallback 이라는 제목의 문제입니다.

두 가지 조건을 만족하면 클리어됩니다.

  1. 컨트렉트의 오너가 됩니다.
  2. 컨트렉트의 잔고를 0으로 만듭니다.

그리고 아래가 도움이 된다고 합니다.

  • ABI와 상호 작용하여 이더를 보내는 방법 : 0번에서 지겹게 했었죠?
  • ABI를 이용하지 않고 이더를 보내는 방법
  • wei/ether 단위를 변경하는 방법 (1 ether = 1,000,000,000,000,000,000 WEI)
  • fallback method : ABI를 호출하지 않고 바로 이더를 컨트렉트로 송금하면 수행됩니다.

이번 문제부터는 Solidity contract 코드가 보이네요. 간략하게 살펴보자면 Fallback이라는 이름의 이 컨트렉트는 contribute(), getContribution(), withdraw() 3개의 함수를 가지고 있고 receive() 함수를 가지고 있네요. 누군가 ABI를 거치지 않고 바로 송금하면 이 함수가 수행됩니다. receive함수는 fallback 함수와는 구분되는데 ... 문제가 오래된건가? ㅎㅎ 너무 고민하지 말아봅시다.

실제로는 7개의 abi를 가지고 있습니다.

코드 전체에서 오너쉽을 획득할 수 있는 부분은 2곳 입니다.

첫번째는 contribute 함수에서 contributions 수량이 기존의 오너보다 많으면 오너쉽을 획득할 수 있습니다.

두번째는 receive 함수에서 보면 보낸 사람의 contributions 수량이 0보다 크고 보낸 금액이 0보다 크면 owner를 넘겨 줍니다.

 

그런데 아래 생성자에 의해 시작부터 오너는 contribution 수량이 1000 이더이기 때문에 첫번째 방법으로 오너쉽을 획득하는 것은 불가능해보입니다.

constructor() public {
  owner = msg.sender;
  contributions[msg.sender] = 1000 * (1 ether);
}

 

그럼 두번째 방법을 살펴봅시다. 1. abi를 통해 contributions가 0보다 크다는 조건을 만족시키고 다음으로 2. abi를 거치지 않고 컨트랙트에 송금하면 이 계약의 owner가 될 수 있을 것 같습니다.

receive() external payable {
  require(msg.value > 0 && contributions[msg.sender] > 0);
  owner = msg.sender;
}

자 여기까지만 생각하고 시도해봅시다.

 

Get new instance 버튼을 눌러 컨트렉트 인스턴스를 생성하고 contribute abi로 약간의 이더를 보내봅시다. 코드를 보면 0.001 이더보다 작게 보내야만 합니다.

function contribute() public payable {
  require(msg.value < 0.001 ether);
  contributions[msg.sender] += msg.value;
  if(contributions[msg.sender] > contributions[owner]) {
    owner = msg.sender;
  }
}

 

콘솔에서 아래를 수행해서 1 wei를 먼저 보내봅시다.

await contract.contribute({value:1})

당연히 수수료도 함께 지불하셔야 하구요. 거래가 완료되면 getContribute()를 통해 확인해봅시다.

await contract.getContribute()

그러면 아래와 같이 1 wei가 있는 것을 확인하실 수 있습니다.

자 이제 첫번째 조건은 만족했구요. 그럼 receive 함수를 실행시키기 위해 이 컨트렉트에 바로 이더를 송금해봅시다. 먼저 컨트렉트의 주소를 알아내기 위해 콘솔에 contract를 입력합니다.

메타마스크를 이용해 이 주소로 이더를 보내봅시다. 전 0.001 ETH만 보내보겠습니다. 수수료도 물론 내셔야 하구요.

이렇게 보내면 receive()함수가 수행될 것이고 그러면 오너가 자신으로 변경되어 있는지 확인해 봅시다.

player(나)와 컨트렉트의 오너가 같다.

자 이제 내가 오너가 되었습니다.

이제는 onlyOwner로 막혀있던 withdraw를 이용해 돈을 빼낼 수 있겠네요. 잔고부터 확인해봅시다. Ethernaut에서 기본으로 제공하는 getBalance 함수를 이용합니다. 물론 컨트렉트의 주소도 읽어오셔야 합니다.

0.001000000000000001을 가지고 있네요 이제 0으로 만들어 봅시다. 아래를 콘솔에서 실행하시고

await contract.withdraw()

그 다음에 잔고를 다시 확인해봅시다.

잔고도 드디어 0가 되었습니다. 입금시켰던 0.001000000000000001 이더도 자신의 계좌로 반환이 된 것을 메타마스크 지갑을 통해 확인하실 수 있습니다. -_- 물론 수수료가 더 나갔지만요 으하하하

 

이제 제출합시다. 주황색 버튼 잊지 않으셨죠? 제출하시면 또 잘했다고 칭찬해 줍니다.

 

자 그럼 다음 레벨에서 만나요~

 

안녕~!

반응형