본문 바로가기
Programing/Solidity

[Solidity] fallback 함수

by AustinProd 2022. 3. 12.

들어가기 앞서 참고해야할 사항이 있다. Solidity는 0.6.0 버전 이후로 fallback 함수가 receive와 fallback으로 나뉘어졌다. 하나의 기능을 세분화시켜 두가지로 나누어 놓은 것이다. 본문은 0.6.0 이후의 fallback 함수에 대해 소개하는 글임을 알린다.

 

Fallback 함수

컨트렉트에서 fallback 함수란 하나의 디폴트 함수와 같다. 이 함수가 실행되는 케이스는 두 가지가 있다. 첫 번째는 호출한 함수가 컨트렉트 내에서 조회되지 않을 경우다. 외부에서 특정 컨트렉트를 호출했을 때, 해당 호출 주소(function identifier)가 확인되지 않으면 디폴트로 fallback 함수가 실행된다. 두 번째는 이더(ETH, ether)를 보낼 때 자동으로 실행된다.

 

쉽게 말해, 스마트 컨트렉트에 함수로 요청을 보냈는데 실제로 없는 함수를 호출하거나 혹은 이더를 보낼 때 fallback 함수가 디폴트로 실행된다.

 

Solidity에서 하나의 컨트렉트는 하나의 fallback 함수를 가질 수 있다. 그리고 다른 일반적인 함수와 조금 다른 형태로 선언된다. 아래 예제 코드를 먼저 살펴보자.

 

 

    # 선언 타입 1
    fallback () external [payable] {
        // ...
    }

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

 

fallback 함수의 특징

fallback 함수의 특징은 다음과 같다.

  • function 키워드가 없다.
  • external 식별자가 붙는다.
  • 함수 이름이 없다. ('fallback'으로 고정)
  • 파라미터를 받지 않는다. 예외적으로 유형 2번에서 byte calldata 형태의 파라미터를 설정하면 컨트렉트에 보내진 모든 데이터(= msg.data)를 반환하게 할 수 있다.
  • 반환값이 없다. 위에서 언급한 2번 유형은 예외
  • payable 옵션 적용 시, 이더를 받고 나서도 실행된다.(기본은 이더 전송(transfer) 시점에서 실행)

 

예제 코드

아래 코드는 공식문서에서 참조한 예제 코드다. 간단하게 변역해두었으니 참고가 되었으면 좋겠다.

 

  // 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;
      }
  }

 

스마트 컨트렉트의 배포는 롤백(roll-back)이 없다. 한 번 배포되면 돌이킬 수 없다. 그렇기 때문에 이 fallback 함수는 블록체인 네트워크의 불변성(immutability)를 보장하는 역할을 한다.

 

지금까지 fallback 함수에 대해 알아보았다. 다음 포스트에서 솔리디티 0.6.0 버전 이후로 나뉘게 된 또 다른 fallback 함수인 receive 함수에 대해 알아보려고 한다.

댓글