TSender

Cyfrin
DeFiFoundry
15,000 USDC
View results
Submission Details
Severity: high
Invalid

DOS in Airdrop function

Summary

Whenever the USDC contract is paused or the recipient is blacklisted in the USDC contract, the interactions where USDC is transferred in the contract will revert.

Vulnerability Details

`function airdropERC20(
    address tokenAddress,
    address[] calldata recipients,
    uint256[] calldata amounts,
    uint256 totalAmount
) external {
    assembly {
        // check for equal lengths
        if iszero(eq(recipients.length, amounts.length)) {
            mstore(0x00, 0x50a302d6) // cast sig TSender__LengthsDontMatch()
            revert(0x1c, 0x04)
        }


        // transferFrom(address from, address to, uint256 amount)
        // cast sig "transferFrom(address,address,uint256)"
        // This will result in memory looking like this:
        // 0x00: 0x23b872dd00000000000000000000000000000000000000000000000000000000
        mstore(0x00, hex"23b872dd")
        // from address
        mstore(0x04, caller())
        // to address (this contract)
        mstore(0x24, address())
        // total amount
        mstore(0x44, totalAmount)


        if iszero(call(gas(), tokenAddress, 0, 0x00, 0x64, 0, 0)) {
            mstore(0x00, 0xfa10ea06) // cast sig "TSender__TransferFailed()"
            revert(0x1c, 0x04)
        }


        // transfer(address to, uint256 value)
        mstore(0x00, hex"a9059cbb")
        // end of array
        // recipients.offset actually points to the recipients.length offset, not the first address of the array offset
        let end := add(recipients.offset, shl(5, recipients.length))
        let diff := sub(recipients.offset, amounts.offset)


        // Checking totals at the end
        let addedAmount := 0
        for { let addressOffset := recipients.offset } 1 {} {
            let recipient := calldataload(addressOffset)


            // Check to address
            if iszero(recipient) {
                mstore(0x00, 0x1647bca2) // cast sig "TSender__ZeroAddress()"
                revert(0x1c, 0x04)
            }


            // to address
            mstore(0x04, recipient)
            // amount
            mstore(0x24, calldataload(sub(addressOffset, diff)))
            // Keep track of the total amount
            addedAmount := add(addedAmount, mload(0x24))


            // transfer the tokens
            if iszero(call(gas(), tokenAddress, 0, 0x00, 0x44, 0, 0)) {
                mstore(0x00, 0xfa10ea06) // cast sig "TSender__TransferFailed()"
                revert(0x1c, 0x04)
            }


            // increment the address offset
            addressOffset := add(addressOffset, 0x20)
            // if addressOffset >= end, break
            if iszero(lt(addressOffset, end)) { break }
        }


        // Check if the totals match
        if iszero(eq(addedAmount, totalAmount)) {
            mstore(0x00, 0x63b62563) // cast sig TSender__TotalDoesntAddUp()
            revert(0x1c, 0x04)
        }
    }
}

`
The user performs some actions in order a to get blacklisted in the USDC contract. For example, by interacting with Tornado Cash. (source: https://thedefiant.io/usdc-addresses-banned)
As withdrawals need to be performed in order, all subsequent calls to send airdrops will revert. If one airdrop recipient is blacklisted by USDC, this creates a DOS in the code.

Impact

That would leave USDC stuck in the contract, so consider using a recovery function to "rescue" all the USDC that was due to these airdrop winners.
DoS or stuck funds

Tools Used

Manual Review

Recommendations

Instead of sending tokens directly to the competition winner, consider storing the number of tokens in variables and having the winners claim it later in order to avoid any revert during the airdrop process.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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