Pieces Protocol

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

Balance updates before the Token Transfer is complated

Summary

The transferErcTokens function modifies internal balances before executing the external transferFrom call, violating the Checks-Effects-Interactions (CEI)

Vulnerability Details

https://github.com/Cyfrin/2025-01-pieces-protocol/blob/4ef5e96fced27334f2a62e388a8a377f97a7f8cb/src/TokenDivider.sol#L179-L207

Medium

Below is the function which is affected

function transferErcTokens(address nftAddress, address to, uint256 amount) external {
if(nftAddress == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
if(to == address(0)) {
revert TokenDivider__CantTransferToAddressZero();
}
if(amount == 0) {
revert TokenDivider__AmountCantBeZero();
}
ERC20Info memory tokenInfo = nftToErc20Info[nftAddress];
if(to == address(0)) {
revert TokenDivider__CantTransferToAddressZero();
}
if(balances[msg.sender][tokenInfo.erc20Address] < amount) {
revert TokenDivider__NotEnoughErc20Balance();
}
balances[msg.sender][tokenInfo.erc20Address] -= amount;
balances[to][tokenInfo.erc20Address] += amount;
emit TokensTransfered(amount, tokenInfo.erc20Address);
IERC20(tokenInfo.erc20Address).transferFrom(msg.sender,to, amount);
}

First state change: Decreases sender's balance before verifying transfer success
If transfer fails, this reduction becomes permanent incorrectly

balances[msg.sender][tokenInfo.erc20Address] -= amount;

Second state change: Increases recipient's balance before verifying transfer

balances[to][tokenInfo.erc20Address] += amount;

External call made after state changes. If this fails, previous state changes aren't reverted

IERC20(tokenInfo.erc20Address).transferFrom(msg.sender,to, amount);

In the contract, the sender’s token balance (balances[msg.sender][tokenInfo.erc20Address]) is reduced before ensuring that the actual token transfer succeeds.
If the transfer fails (e.g., due to insufficient allowance or network issues), the balance reduction remains permanent, even though the tokens weren’t sent. Similarly, the recipient’s token balance (balances[to][tokenInfo.erc20Address]) is increased before the external transfer is verified.
If the transfer fails, the recipient’s balance incorrectly shows tokens they never received. The actual token transfer (IERC20(tokenInfo.erc20Address).transferFrom) happens after the state changes. If this call fails, the balances are already modified incorrectly, and there’s no mechanism to automatically reverse these changes.

Impact

  1. If the external transferFrom call fails, Internal balances become permanently out of sync with actual token balances if transfer fails

  2. Users could lose access to their tokens due to incorrect balance records

  3. Tokens are essentially lost in the system due to incorrect state updates.

Recommendation

The Contract should Perform the token transfer first using IERC20.transferFrom and then Only update the balances after verifying the transfer succeeded.

IERC20(tokenInfo.erc20Address).transferFrom(msg.sender, to, amount);
balances[msg.sender][tokenInfo.erc20Address] -= amount;
balances[to][tokenInfo.erc20Address] += amount;
emit TokensTransfered(amount, tokenInfo.erc20Address);
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.