fallback 함수는 무기명 함수 즉, 이름이 없는 함수이며 컨트랙트에서 하나의 디폴트 함수와 같다.
fallback 함수는 직접 호출되지 않으며, 다음과 같은 상황에서 실행된다.
다시 말해, 스마트 컨트렉트에 함수로 요청을 보냈는데 실제로 없는 함수를 호출하거나 또는 이더를 보낼 때 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
으로 고정)byte calldata
형태의 파라미터를 설정하면 컨트랙트에 보내진 모든 데이터(= msg.data
)를 반환하게 할 수 있다.msg.data
)를 반환하게 할 수 있다.virtual
키워드를 가질 수 있기 때문에 override 할 수 있다. 아래 예제는 솔리티디 공식 문서 내에 존재하는 예제이다.
// 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;
}
}