TSender

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

Lack of Duplicate Address Check in airdropERC20 Function

Summary

The airdropERC20 function in the TSender contract does not include a check for duplicate addresses in the recipients array. Although the areListsValid function performs this check, it is not enforced within the airdropERC20 function, allowing duplicate addresses to be processed without validation.

Proof of Concept

Consider the following non-code PoC scenario demonstrating the issue:

1: An airdrop is planned to distribute 1000 tokens among several recipients.

2: Invalid Lists:

  • The recipients array contains duplicate addresses: [0x123, 0x456, 0x123, 0x789].

  • The amounts array includes valid amounts: [100, 200, 300, 400].

3: The airdropERC20 function is called with these invalid lists.

4: Results:

  • The address 0x123 receives tokens twice (100 + 300 = 400 tokens), while other addresses receive their specified amounts.

  • Total distributed tokens: 1000 (intended distribution), but with an unintended allocation due to duplicates.

Impact

  • If the recipients array contains duplicate addresses, the same address may receive multiple transfers, leading to unintended token distribution.

  • Tokens may be distributed to the same address multiple times, which is inefficient and not the intended behavior of an airdrop.

  • Malicious users could exploit this flaw by including their address multiple times in the recipients array to receive more tokens than intended.

Tools Used

Manual review

Recommendations

To address the lack of duplicate address checks, the airdropERC20 function should call the areListsValid function at the beginning and revert if the validation fails. Here’s how you can integrate the validation:

function airdropERC20(
address tokenAddress,
address[] calldata recipients,
uint256[] calldata amounts,
uint256 totalAmount
) external {
// Validate the input arrays
require(areListsValid(recipients, amounts), "TSender__InvalidInput");
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)
mstore(0x00, hex"23b872dd")
mstore(0x04, caller())
mstore(0x24, address())
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")
let end := add(recipients.offset, shl(5, recipients.length))
let diff := sub(recipients.offset, amounts.offset)
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)
}
}
}
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.