👓Examples

Understanding the Examples: In the "Examples" section, we delve into practical instances of Fee-On-Transfer vulnerabilities unearthed from actual bug bounty reports, Code Arena analyses, and more. By studying these real-life cases, you'll gain a deeper, more tangible understanding of Fee-On-Transfer and its potential impact, enhancing your auditing skills in a practical and impactful way.

Example 1: Accounting Error with Fee-on-Transfer Tokens

Real-life Cod4rena Bug Find With Payout

Consider a smart contract that assumes the number of tokens it receives from a transfer is exactly equal to the _amount specified by the sender. This assumption holds true for standard ERC20 tokens, but it leads to accounting errors when dealing with fee-on-transfer tokens that subtract transaction fees from the sender's remaining balance, not from the transferred amount.

In the context of the contract, let's say we have the following function:

function createPromotion(address _token, uint256 _amount) public {
    TokenBalance[_token] += _amount;
    _token.safeTransferFrom(msg.sender, address(this), _amount);
}

This function increments the contract's tracked balance of _token by _amount and then performs a safe transfer from the sender to the contract. But, if _token is a fee-on-transfer token, the actual number of tokens received by the contract is less than _amount because of the deducted fee. This results in the TokenBalance[_token] being larger than the actual number of tokens held by the contract, leading to an accounting error.

To fix this, we need to calculate the actual balance of tokens received during the transaction by comparing the balance before and after the transfer:

Recommended Mitigation Steps:

function createPromotion(address _token, uint256 _amount) public {
    uint256 oldBalance = _token.balanceOf(address(this));
    _token.safeTransferFrom(msg.sender, address(this), _amount);
    uint256 newBalance = _token.balanceOf(address(this));
    require(oldBalance + _amount == newBalance, "Fee-on-transfer tokens not allowed");
}

In this version of createPromotion, before executing the transfer, the contract saves the old balance of _token. After the transfer, it checks if the _amount of tokens specified by the sender matches the actual increase in the contract's token balance. If they do not match, it means a fee was deducted from the transferred amount, and the function reverts with the message "Fee-on-transfer tokens not allowed". This way, only tokens that do not deduct transfer fees can interact correctly with the contract, preventing accounting errors.

General Pattern

In smart contract development, a common pattern has emerged around vulnerabilities related to fee-on-transfer tokens. The core issue typically originates when a protocol accepts external tokens and updates its internal token balance mapping for a certain functionality, such as a liquidity pool. This protocol might operate under the assumption that all tokens transferred into it do not carry any fees, which leads to an unexpected discrepancy.

The primary characteristic of these vulnerabilities is the mismatch created between the actual balance of tokens held by the contract and the internal representation of that balance, as maintained by the smart contract (typically via a mapping). The contract expects the exact amount of tokens specified in the transfer call to be received, but for fee-on-transfer tokens, a portion of the tokens is deducted as a fee during the transaction. Consequently, the actual number of tokens received by the contract is less than the amount used to update the balance mapping.

This accounting discrepancy can trigger a series of problems. For instance, when users attempt to withdraw their tokens, the transaction may fail. This happens because the balance mapping could indicate that users are entitled to more tokens than the contract actually holds, causing a shortage during the withdrawal process.

These inconsistencies expose the contract to potential exploits and functional issues. Therefore, it is crucial for developers to consider the potential impact of transfer fees when designing protocols that interact with external tokens. This will help in avoiding mismatches between actual balances and their internal representations, thus ensuring robust and secure contract operations.

Last updated