Sparkn

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

Fee on transfer tokens lead to loss of commission fees and DoS / frozen funds

Summary

Some ERC20 tokens, such as USDT, allow for charging a fee any time transfer() or transferFrom() is called, and the protocol devs stated out that major stable coins like USDC, USDT, or JPYC will be used. A token which makes use of such fees will lead to a loss of commission fees and a DoS / frozen funds in the worst case.

Vulnerability Details

When an organizer wants to send funds to the winners of a contest, a proxy must be deployed and the distribute function must be called:

function deployProxyAndDistribute(bytes32 contestId, address implementation, bytes calldata data)
public
returns (address)
{
bytes32 salt = _calculateSalt(msg.sender, contestId, implementation);
if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
// can set close time to current time and end it immediately if organizer wish
if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed();
address proxy = _deployProxy(msg.sender, contestId, implementation);
_distribute(proxy, data);
return proxy;
}

The distribute function loops over multiple arrays of arbitrary length and calls a ERC-20 transfer function:

function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)
internal
{
// token address input check
if (token == address(0)) revert Distributor__NoZeroAddress();
if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}
// winners and percentages input check
if (winners.length == 0 || winners.length != percentages.length) revert Distributor__MismatchedArrays();
uint256 percentagesLength = percentages.length;
uint256 totalPercentage;
for (uint256 i; i < percentagesLength;) {
totalPercentage += percentages[i];
unchecked {
++i;
}
}
// check if totalPercentage is correct
if (totalPercentage != (10000 - COMMISSION_FEE)) {
revert Distributor__MismatchedPercentages();
}
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this));
// if there is no token to distribute, then revert
if (totalAmount == 0) revert Distributor__NoTokenToDistribute();
uint256 winnersLength = winners.length; // cache length
for (uint256 i; i < winnersLength;) {
uint256 amount = totalAmount * percentages[i] / BASIS_POINTS;
erc20.safeTransfer(winners[i], amount);
unchecked {
++i;
}
}
// send commission fee as well as all the remaining tokens to STADIUM_ADDRESS to avoid dust remaining
_commissionTransfer(erc20);
emit Distributed(token, winners, percentages, data);
}

If the ERC-20 token which is called uses fees, every transfer call will subtract more than expected from the contract balance. All these fees will be subtracted from the commission fee that the owner of the contract receives at the end. If the transfer fees exceed the commission fee, it will even lead to a DoS / frozen funds as one of the transfer calls will revert.

Impact

Loss of commission fees and DoS / frozen funds.

Tools Used

Manual Review, Foundry, VSCode

Recommendations

Calculate with these fees, or prevent the usage of such tokens.

Support

FAQs

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