Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: medium

Inconsistent Behavior of ERC-20 Token Transfer Functions

Summary

The issue stems from the historical variability in the behavior of transfer functions among different ERC-20 tokens. Early tokens were developed before the finalization of the ERC-20 standard, leading to discrepancies in whether transfer functions should revert or return a boolean value. This inconsistency in behavior poses challenges when interacting with these tokens and integrating them into smart contracts.

Vulnerability Details

Some tokens, although claiming ERC-20 compatibility, deviate from the current best practice regarding transfer functions. The inconsistency lies in whether a transfer function a) should revert/fail on error and return a boolean value (true on success and false on failure) or b) should only return true or false without reverting on error.

For example, consider a contract similar to the "Distributor" contract provided earlier. In the _distribute function, the contract interacts with the ERC-20 token using the balanceOf function to determine the available balance for distribution. However, if the token being used adheres to the incorrect behavior (returning only true or false on success), the contract might receive incorrect information about the token balance.

function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)
internal
{
// ...
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this)); // Potential vulnerability
// ...
}

Impact

The inconsistent behavior of ERC-20 token transfer functions can lead to incorrect calculations, potential underestimation of available token balance, and improper distribution of tokens. This can result in financial discrepancies, errors in distribution percentages, and unexpected behavior in smart contract execution.

Tools Used

Manual

Recommendations

Before interacting with any ERC-20 token, verify that the token's transfer functions adhere to the expected behavior of reverting/failing on error and returning true on success.

  1. Define an interface for ERC-20 tokens that includes the standard transfer function signature:

interface IStandardERC20 {
function transfer(address to, uint256 amount) external returns (bool);
}
  1. Modify your _isWhiteListed function to include a check for token compatibility:

function _isWhiteListed(address token) internal view returns (bool) {
IStandardERC20 erc20 = IStandardERC20(token);
try erc20.transfer(address(0), 0) returns (bool success) {
return true; // Token is compatible with expected behavior
} catch {
return false; // Token does not conform to expected behavior
}
}
  1. Before using the token's balance in your _distribute function, validate its compatibility:

function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)
internal
{
// ...
if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this)); // Safe to use now
// ...
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.