Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Invalid

[M-1] Unchecked Transfer Return Value in `TokenDivider::transferErcTokens`

Summary

The transferErcTokens function in the TokenDivider contract uses IERC20(tokenInfo.erc20Address).transferFrom to transfer ERC20 tokens but does not check the return value of the transferFrom call. If the transferFrom call fails (e.g., due to insufficient allowance or a non-compliant ERC20 token), the function will not revert, leading to a silent failure. This can result in incorrect state updates (balances being deducted/added) without the actual token transfer taking place, causing inconsistencies in the contract's state.

Vulnerability Details

The transferErcTokens function updates the sender's and recipient's balances before calling transferFrom. If the transferFrom call fails silently (e.g., returns false instead of reverting), the contract state will reflect a successful transfer even though no tokens were moved. This inconsistency can lead to users losing tokens or gaining balances they cannot use.

The following code demonstrates the vulnerability:

function transferErcTokens(address nftAddress, address to, uint256 amount) external {
// ... (checks omitted for brevity)
balances[msg.sender][tokenInfo.erc20Address] -= amount;
balances[to][tokenInfo.erc20Address] += amount;
emit TokensTransfered(amount, tokenInfo.erc20Address);
// Unchecked transferFrom call
IERC20(tokenInfo.erc20Address).transferFrom(msg.sender, to, amount); // No return value check
}

Exploit Scenario:

A non-compliant ERC20 token returns false instead of reverting on failure (e.g., due to insufficient allowance or balance).

A user calls transferErcTokens to transfer tokens, but the transferFrom call fails silently.

The contract deducts the tokens from the sender's balance and adds them to the recipient's balance, even though no tokens were actually transferred.

The sender loses tokens, and the recipient gains a balance they cannot use.

Impact

Medium Impact: Users may lose tokens if the transferFrom call fails silently, as the contract state will reflect a successful transfer even though no tokens were moved.

State Inconsistency: The contract's state (balances) will be inconsistent with the actual token balances, leading to potential financial losses for users.

User Dissatisfaction: Users may lose trust in the protocol if they experience unexpected losses.

Tools Used

Manual review, Foundry

Recommendations

Always check the return value of transferFrom and revert if the transfer fails. Use a require statement to ensure the transfer is successful. For example:

function transferErcTokens(address nftAddress, address to, uint256 amount) external {
// ... (checks omitted for brevity)
balances[msg.sender][tokenInfo.erc20Address] -= amount;
balances[to][tokenInfo.erc20Address] += amount;
emit TokensTransfered(amount, tokenInfo.erc20Address);
// Check the return value of transferFrom
bool success = IERC20(tokenInfo.erc20Address).transferFrom(msg.sender, to, amount);
require(success, "Token transfer failed");
}

Additional Considerations:

Use OpenZeppelin's SafeERC20 library, which provides safe wrappers around ERC20 operations and automatically handles non-compliant tokens.

Consider adding events for failed transfers to improve transparency and debugging.

Updates

Lead Judging Commences

fishy Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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