Solidity v0.6.0. Fallback functions. What are they needed for?

Viewed 2238

After reading the documentation for Solidity v0.6.0 docs, I still don't understand the meaning of the fallback functions. I read that it was split into 2 functions: fallback () external payable and receive () external payable. That they are anonymous and do not accept any parameters, and in the overwhelming majority of cases, receive () external payable is used to receive funds. Can you please explain with the example of my code, some use cases for these functions, in order to understand all their features, otherwise somehow everything is in a vacuum, but I understand that this is an important concept? Even the meaning of the receive () external payable function is not clear, in which I call on the buyToken () method, why is it needed if I call on the buyToken () in the Remix directly, bypassing the receive () external payable since she is not visible and anonymous.

pragma solidity ^0.7.0;
    // SPDX-License-Identifier: MIT
    
    contract BuyToken {
      mapping(address => uint256) public balances;
      address payable wallet;
    
      event Purchase(
        address indexed buyer,
        uint256 amount
      );
    
      constructor(address payable _wallet) {
        wallet = _wallet;
      }
    
    
      fallback() external payable {
      }
    
    
      receive() external payable {
        buyToken();
      }
    
      function buyToken() public payable {
        balances[msg.sender] += 1;
        wallet.transfer(msg.value);
        emit Purchase(msg.sender, 1);
      }
    }
3 Answers

When the sender sends ETH to your contract address and doesn't specify any function (i.e. the data field of the tx is empty), the receive() gets executed.

Since the receive() just calls buyToken(), it produces the same set of actions as if the user executed the buyToken() directly.

But other contracts can make a different use of the receive() function. Example of a simple bank contract:

pragma solidity ^0.8;

contract MyContract {
    mapping (address => uint256) public balances;

    receive() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 _amount) external {
        require(_amount <= balances[msg.sender], 'Insufficient balance');
        balances[msg.sender] -= _amount;
        payable(msg.sender).transfer(_amount);
    }
}

Or a timelock:

pragma solidity ^0.8;

contract MyContract {
    uint256 public constant unlockAfter = 1640995200; // 2022-01-01

    receive() external payable {
        // anyone can send funds to this contract
    }

    function withdraw() external {
        require(msg.sender == address(0x123), 'Not authorized');
        require(block.timestamp >= unlockAfter, 'Not unlocked yet');
        payable(msg.sender).transfer(address(this).balance);
    }
}

The fallback() functions is used when the function signature (first 4 bytes of the data field) doesn't match any of the existing functions.

pragma solidity ^0.8;

contract MyContract {
    function foo() external {
        // executed when the `data` field starts with `0xc2985578`, the signature of `foo()`
    }
    
    fallback() external {
        // executed when the `data` field is empty or starts with an unknown function signature
    }
}

I'm not sure about your code example but here it goes:
Fallback function - I think here is a good explanation. So if not marked payable, it will throw exception if contract receives plain ether without data.
External payable - This post explains External well. So it costs less gas to call external than public. Only in your example it would make sense to change buyToken() from "public" to "external". Far as I understand there is no benefit to call public from external...

The fallback functions are invoked automatically by the Ethereum Virtual Machine (EVM) which is why they are marked as external. Since it cannot be called explicitly, gas cannot be sent explicitly to this function. Instead, the EVM provides a fixed stipend of 2,300 gas for this function. If the gas cost is more than 2300 an exception will be thrown. It is important that you should be testing fallback function's gas consumption before deploying your contract

contract Fallback {
    fallback () external payable {
    }
}
  • It has no arguments and return data. It gets executed every time ether is received. It is required to be implemented within a contract if the contract is intended to receive ether; otherwise, an exception will be thrown and ether will be returned.

  • This function also executes if no other function signatures match in the contract.

  • If the contract is expected to receive ether, then the fallback function has to be declared with the payable modifier.

  • Another use case is for proxy contracts which routes the incoming requests to the upgradeable target contract. the fallback function in proxy contract actually calls the target contract's functions.

    function _fallback() internal virtual {
          _beforeFallback();
           // calling the target function code
          _delegate(_implementation());
      }
    

fallback function is called under multiple conditions and this makes writing code for each condition hard. That is why receive is added. receive is called only contract receives ether so that fallback should be executed only if a function signature is not implemented within a contract.

Related