Post

Fallback

Fallback

이번글에서는 Ethernaut의 ‘Fallback’이란 문제를 풀어보겠습니다.

0x01 Fallback Function


Fallback 함수는

  1. 존재하지 않는 함수가 호출된 경우
  2. Ether가 컨트랙트로 직접 전송되었지만 receive()가 없는 경우
  3. msg.data가 비어있지 않은 경우

호출됩니다.

아래는 Fallback 함수의 동작 조건(2번, 3번)을 시각화한 것입니다.

1
2
3
4
5
6
7
8
9
10
11
                 send Ether
                      |
           msg.data is empty?
                /           \
            yes             no
             |                |
    receive() exists?     fallback()
        /        \
     yes          no
      |            |
  receive()     fallback()

Reference | solidity-by-example

0x02 Features


  • Fallback은 익명함수이다.
  • 파라미터와 리턴 값이 없으며, 특별한 경우 데이터(bytes calldata)를 인자로 받아 처리할 수 있다.
  • Fallback은 무조건 external로 선언되어야 한다.
  • Fallback 함수가 payable로 선언되면, 이더를 받을 때 실행 가능하다.
  • Fallback은 전송이나 전송을 통해 호출될 때 2300 가스 제한을 갖는다.
  • Fallback은 직접 호출되지 않는다.

0x03 Examples


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract Fallback {
    // event: 함수 이름과 남은 가스량을 로그로 기록
    event Log(string func, uint256 gas);

    // fallback()
    // - 컨트랙트에 정의되지 않은 함수가 호출되거나, 데이터가 담긴 트랜잭션이 전송될 때 실행됨
    // - 반드시 external 및 payable로 선언 (이더 수신 가능)
    // - send/transfer: gas 2300만 전달, call: 모든 gas 전달
    fallback() external payable {
        emit Log("fallback", gasleft());
    }

    // receive()
    // - msg.data(입력 데이터)가 비어있는 단순 이더 전송 시 호출됨
    // - 반드시 external 및 payable로 선언 (이더 수신 가능)
    receive() external payable {
        emit Log("receive", gasleft());
    }

    // getBalance(): 이 컨트랙트의 현재 이더 잔고를 반환
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

contract SendToFallback {
    // transferToFallback()
    // - _to 주소로 transfer를 사용하여 이더를 보냄
    // - transfer는 2300 가스만 전달 → 대상 컨트랙트의 receive 또는 fallback 함수가 실행됨(단, receive 함수가 있으면 우선)
    function transferToFallback(address payable _to) public payable {
        _to.transfer(msg.value);
    }

    // callFallback()
    // - _to 주소로 call을 사용하여 이더를 보냄
    // - call은 모든 가스를 전달하며, 데이터도 함께 보낼 수 있음
    // - 함수 시그니처 등 데이터가 없으면 receive, 데이터가 있으면 fallback이 실행됨
    // - 이더 전송 성공 여부 확인(실패시 revert)
    function callFallback(address payable _to) public payable {
        (bool sent,) = _to.call{value: msg.value}("");
        require(sent, "Failed to send Ether");
    }
}

Reference | solidity-by-example

위의 코드를 실행 시 생기는 CALLDATA는 msg.data 역할을 합니다.

즉, CALLDATA에 빈 값을 넣으면 CALLDATA(msg.data)가 비어있으므로 receive가, 빈 값이 아니라면 fallback이 호출됩니다.

This post is licensed under CC BY 4.0 by the author.