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();
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
{
if (token == address(0)) revert Distributor__NoZeroAddress();
if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}
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;
}
}
if (totalPercentage != (10000 - COMMISSION_FEE)) {
revert Distributor__MismatchedPercentages();
}
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this));
if (totalAmount == 0) revert Distributor__NoTokenToDistribute();
uint256 winnersLength = winners.length;
for (uint256 i; i < winnersLength;) {
uint256 amount = totalAmount * percentages[i] / BASIS_POINTS;
erc20.safeTransfer(winners[i], amount);
unchecked {
++i;
}
}
_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.