[Solidity] fallback function

bolee·2022년 12월 17일
0

솔리디티(Solidity)

목록 보기
5/8
post-thumbnail
post-custom-banner

fallback function

fallback 함수는 무기명 함수 즉, 이름이 없는 함수이며 컨트랙트에서 하나의 디폴트 함수와 같다.
fallback 함수는 직접 호출되지 않으며, 다음과 같은 상황에서 실행된다.

  • 호출한 함수가 컨트렉트 내에서 조회되지 않을 경우
    • 외부에서 특정 컨트렉트를 호출했을 때, 해당 호출 주소(function identifier)가 확인되지 않으면 디폴트로 fallback 함수가 실행된다.
  • 이더(ETH, ether)를 보낼 때 자동으로 실행

다시 말해, 스마트 컨트렉트에 함수로 요청을 보냈는데 실제로 없는 함수를 호출하거나 또는 이더를 보낼 때 fallback 함수가 디폴트로 실행된다.
fallback 함수는 디폴트 함수와 같기 때문에 solidity에서 하나의 컨트렉트는 하나의 fallback 함수를 가질 수 있다.

solidity는 0.6.0 버전 이후로 fallback 함수가 receive와 fallback으로 나뉘어졌다.
0.6.0 버전 이전의 fallback 함수는 이더를 보내거나 받을 때 실행되지만 0.6.0 버전 이후의 fallback 함수는 이더를 보낼 때 실행되고, 이더를 받을 때는 receive 함수가 실행된다.
즉, 하나의 기능을 세분화시켜 두가지로 나누어 놓은 것이다. 여기에서는 0.6.0 버전 이후의 fallback 함수를 소개할 것이다.

선언 형태

fallback 함수는 다음과 같은 형태로 선언된다.

// 선언 유형 1
fallback () external [payable] {
	// ...
}

// 선언 유형 2
fallback (bytes calldata _input) external [payable] returns (bytes memory _output) {
	// ...
}

특징

  • function 키워드가 없다.
  • external 식별자가 붙는다.
  • 함수 이름이 없다. (fallback으로 고정)
  • 파라미터(parameter)를 받지 않는다.
    • 2번째 유형에서 byte calldata 형태의 파라미터를 설정하면 컨트랙트에 보내진 모든 데이터(= msg.data)를 반환하게 할 수 있다.
  • 반환값이 없다.
    • 2번째 유형의 경우 컨트랙트에 보내진 모든 데이터(= msg.data)를 반환하게 할 수 있다.
  • payable 옵션 적용 시, 이더를 받고 나서도 실행된다.
    • 기본은 이더 전송(transfer) 시점에서 실행된다.
  • payable fallback 함수에서는 2300 gas 제한이 존재한다.
  • virtual 키워드를 가질 수 있기 때문에 override 할 수 있다.
  • modifier들을 가질 수 있다.

예제

아래 예제는 솔리티디 공식 문서 내에 존재하는 예제이다.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;


contract Test {
    uint x;

    // 해당 코드는 Test 컨트렉트에 보내지는 모든 메세지에 대한 응답 코드로 작성하였다.
    //     ->    fallback 함수는 컨트렉트에서 조회한 함수가 없을 때 호출되기 때문
    // 이 컨트렉트 주소로 보내지는 이더를 전송(transfer) 요청은 예외를 발생시킬 수 있다.
    //    ->    `payable` 옵션이 없기 때문
    fallback() external { x = 1; }
}


contract TestPayable {
    uint x;
    uint y;

    // 이더를 보내는 요청이 아닌 모든 요청에서 fallback이 실행된다. - 컨트렉트 어카운트에서 이더 전송
    // 데이터를 포함한 요청에서 fallback 함수를 실행하게 한다.
    fallback() external payable { x = 1; y = msg.value; }

    // 이더를 보내는 요청에서 아래 함수는 실행된다. - 컨트렉트 어카운트로 이더 받음
    // 데이터가 포함되어 있지 않은 요청에서 함수 실행
    receive() external payable { x = 2; y = msg.value; }
  }


contract Caller {
    function callTest(Test test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));

        // 조건식 -> test.x == 1
        require(success);

        // address(test)는 fallback 함수에 payable 옵션이 없기 때문에 send 요청을 할 수 없다.
        //    ->    address(test)는 address payable으로 변환 되어야 한다.
        address payable testPayable = payable(address(test));

        // 먄약 누군가 이더를 아래와 같이 보낸다면, 요청을 실패할 것이다. (return = false) 
        return testPayable.send(2 ether);
    }

    function callTestPayable(TestPayable test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));

        // 조건식 -> test.x == 1 && test.y == 0
        require(success);

        (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));

        // 조건식 -> test.x == 1 && test.y == 1
        require(success);

        // 누군가 이더를 아래와 같이 이더를 보내면 해당 컨트렉트의 receive 함수가 호출될 것이다.
         //    ->    실제 이더 전송에 필요한 가스는 더 많이 필요할 것이다. 해당 함수가 storage에 접근하기 때문
        (success,) = address(test).call{value: 2 ether}("");

        // 조건식 -> test.x == 2 && test.y == 2
        require(success);

        return true;
    }
}

참고 자료

post-custom-banner

0개의 댓글