1. 문제

아래의 컨트랙트 코드를 잘 살펴보고 다음의 문제를 해결하자.    

1. 컨트랙트의 소유권을 탈취해라.
2. 컨트랙트의 이더 잔액(balance)을 0으로 만들어라.
pragma solidity ^0.8.0;

contract Fallback {
    mapping(address => uint256) public contributions;
    address public owner;

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

    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }

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

    function getContribution() public view returns (uint256) {
        return contributions[msg.sender];
    }

    function withdraw() public onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

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

2. Fallback이란?

fallback 함수는 컨트랙트에 정의되어 있지 않은 함수가 호출될 경우 자동으로 실행되는 함수이다.

예를 들어, 외부에서 컨트랙트에 존재하지 않는 함수를 호출하거나, 데이터가 포함된 트랜잭션을 전송했을 때 이 함수가 실행된다.

또한, 단순히 계정 간 이더를 보내는 transfer() 또는 send()와 같은 함수도 내부적으로 컨트랙트 호출이기 때문에, 해당 컨트랙트에 정의된 함수가 없을 경우 fallback이 호출된다.

종류 호출 조건 용도
receive() calldata가 비어 있고, 함수명이 없으며, 컨트랙트가 ETH를 받기 위한 용도일 때 순수 이더 수신용
fallback() calldata가 존재하거나, receive() 함수가 없을 경우 데이터 포함 호출 대응

두 함수 차이

3. 컨트랙트의 소유권 탈취

이번 문제에서 owner를 탈취할 수 있는 방법은 2가지가 존재한다