Empty Payable Fallback
Overview
- Severity: Low
- Confidence: Medium
- Affected Versions: All
What is the Empty Payable Fallback vulnerability?
In Solidity, two special functions (the fallback function and receive()
function) can be used as an explicit entry point for a smart contract to accept ether. If these functions are payable
but have no bodies, it may be an indication of incomplete contract logic; contracts that explicitly mean to receive ether should have some logic for handling it when it arrives.
Further reading: Solidity Documentation: Special Functions
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleEtherWallet {
// Total Ether received by the contract
uint256 public totalReceived;
// Mapping to track Ether balances per sender
mapping(address => uint256) public senderBalances;
// Possible confusion of payable functions for depositing Ether
receive() external payable { }
function deposit() external payable {
senderBalances[msg.sender] += msg.value;
totalReceived += msg.value;
}
// Other management functionality omitted for brevity
}
In the example above, contract SimpleEtherWallet
is clearly designed to handle and store ether on behalf of users. However, it offers two different ways of sending ether to the contract: a recieve()
function which is payable
, and the deposit()
function. Of these two, only the latter has logic for managing accounting of ether balances, which may lead to user confusion when attempting to send ether to the contract, and may represent an oversight on the part of the developer.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleEtherWalletUpdated {
// Total Ether received by the contract
uint256 public totalReceived;
// Mapping to track Ether balances per sender
mapping(address => uint256) public senderBalances;
// Possible confusion of payable functions for depositing Ether
receive() external payable {
senderBalances[msg.sender] += msg.value;
totalReceived += msg.value;
}
// Other management functionality omitted for brevity
}
In the revised example above, contract SimpleEtherWalletUpdated
has eliminated the deposit()
function and now includes accounting for ether balances directly in the receive()
function. Equally valid would be to remove the receive()
function from the prior example and have only the deposit()
function manage incoming ether. In either case, the logic for handling incoming ether payments should be clear to both the developer and the user.