Summary

The RefundFeesOnChannelClosure function refunds all the packets’ fees when a channel is closed. If multiple fees exist for a packet ID (identifiedPacketFee.PacketFees variable), an inner loop will be executed to distribute the funds from the feeibc module to the refund address. If the sdk.AccAddressFromBech32(packetFee.RefundAddress) or SendCoinsFromModuleToAccount functions in lines 208 or 215 fail, the failedToSendCoins boolean will be set to true, causing the packet ID not to be removed from the storage.

The issue is that the packet will not be deleted as long as one of the fee transfers fails. In other words, successful packet fees are not removed from the packet ID storage. This means that the storage will record both successful and failed fee transfers, which is incorrect because successful fee transfers need to be removed, as the funds have already been received by the recipient (notice that the cached context is committed).

If the governance decides to refund the failed fee transfers by checking the packet IDs in storage (e.g., using the GetAllIdentifiedPacketFees function), the recipient will receive twice the refund amount. This may also happen during future developments that allow the receiver to initiate a refund for closed channels.

Proof of concept

Workarounds

Consider modifying the RefundFeesOnChannelClosure function so that when a packet fee is transferred successfully, it is excluded from the packet ID. If all the fees are removed, call DeleteFeesInEscrow to remove the packet ID from the storage. If there are remaining fees, call SetFeesInEscrow to update the packet ID in the storage.

Fix

Fixed in https://github.com/cosmos/ibc-go/pull/6255 from https://github.com/cosmos/ibc-go/releases/tag/v7.5.0.