TSender

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

[M-03] Lack of Safe Transfer Checks in `TSender` Contract

Summary

The function airdropERC20() in theTSender contract, designed to airdrop ERC20 tokens to multiple recipients, uses the low-level call function for token transfers. However, this approach does not handle the case where the ERC20 token might return false instead of reverting. This can cause silent failures, potentially leading to incomplete airdrops and incorrect assumptions about the success of transfers.

Vulnerability Details

The contract uses call for token transfers but does not handle the case where the ERC20 token might return false instead of reverting. This can cause silent failures if the token transfer fails, resulting in the contract incorrectly assuming the transfer succeeded.

Impact

If the transfer fails due to reasons such as insufficient balance or issues within the token contract, the contract may proceed under the incorrect assumption that the transfer succeeded. This can lead to:

  • Incomplete Airdrops: Some recipients may not receive their tokens, resulting in an incomplete distribution of the intended airdrop.

  • Silent Failures: The contract does not detect and handle the failure, leading to incorrect state assumptions and potential discrepancies in the airdrop process.

Tools Used

  • Manual Review

Recommendations

To address this issue, it is recommended to implement safe transfer checks in Yul to handle token transfers safely. This ensures proper handling of token transfers by checking return values and reverting if the transfer fails.

Revised Implementation in Yul
Here is the revised implementation of the airdropERC20 function using safe transfer checks in Yul:

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)
}
// Initialize total amount check
let addedAmount := 0
// Pre-transfer validation: Calculate the actual total amount
let end := add(recipients.offset, shl(5, recipients.length))
let diff := sub(recipients.offset, amounts.offset)
for { let addressOffset := recipients.offset } lt(addressOffset, end) { addressOffset := add(addressOffset, 0x20) } {
let recipient := calldataload(addressOffset)
// Check for zero address
if iszero(recipient) {
mstore(0x00, 0x1647bca2) // cast sig "TSender__ZeroAddress()"
revert(0x1c, 0x04)
}
// Calculate total amount
addedAmount := add(addedAmount, calldataload(sub(addressOffset, diff)))
}
// Check if the totals match
if iszero(eq(addedAmount, totalAmount)) {
mstore(0x00, 0x63b62563) // cast sig TSender__TotalDoesntAddUp()
revert(0x1c, 0x04)
}
// Perform transferFrom to the contract
mstore(0x00, hex"23b872dd") // transferFrom(address,address,uint256) function selector
mstore(0x04, caller())
mstore(0x24, address())
mstore(0x44, totalAmount)
let success := call(gas(), tokenAddress, 0, 0x00, 0x64, 0, 0)
if iszero(success) {
mstore(0x00, 0xfa10ea06) // cast sig "TSender__TransferFailed()"
revert(0x1c, 0x04)
}
// Perform transfers to recipients
mstore(0x00, hex"a9059cbb") // transfer(address,uint256) function selector
for { let addressOffset := recipients.offset } lt(addressOffset, end) { addressOffset := add(addressOffset, 0x20) } {
let recipient := calldataload(addressOffset)
// to address
mstore(0x04, recipient)
// amount
mstore(0x24, calldataload(sub(addressOffset, diff)))
// Transfer the tokens
let transferSuccess := call(gas(), tokenAddress, 0, 0x00, 0x44, 0, 0)
if iszero(transferSuccess) {
mstore(0x00, 0xfa10ea06) // cast sig "TSender__TransferFailed()"
revert(0x1c, 0x04)
}
// Check if the transfer returned a boolean value and ensure it is true
switch returndatasize()
case 0 {
// If no data is returned, assume the transfer was successful
}
case 0x20 {
returndatacopy(0x00, 0x00, 0x20)
if iszero(mload(0x00)) {
mstore(0x00, 0xfa10ea06) // cast sig "TSender__TransferFailed()"
revert(0x1c, 0x04)
}
}
default {
mstore(0x00, 0xfa10ea06) // cast sig "TSender__TransferFailed()"
revert(0x1c, 0x04)
}
}
}
}
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.