H-M01: fix(contracts/ChannelHub): remove transferred amount check#636
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR replaces ERC20 transfer failure detection in ChannelHub's Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant ChannelHub
participant Token
rect rgba(100, 149, 237, 0.5)
note over Caller,Token: New Return-Value-Based Transfer Failure Detection
Caller->>ChannelHub: _pushFunds(to, token, amount)
ChannelHub->>ChannelHub: _trySafeTransfer(token, to, amount)
ChannelHub->>Token: transfer{gas: TRANSFER_GAS_LIMIT}(to, amount)
alt Transfer Success (returns true)
Token-->>ChannelHub: return true
ChannelHub-->>Caller: Proceed (no reclaim credit)
else Transfer Failure (returns false or reverts)
Token-->>ChannelHub: return false or revert
ChannelHub->>ChannelHub: _reclaims[to][token] += amount
ChannelHub->>ChannelHub: emit TransferFailed(...)
ChannelHub-->>Caller: Failure handled, reclaim credited
end
end
rect rgba(255, 165, 0, 0.5)
note over Caller,Token: Donation-Back Edge Case (Handled Correctly)
Caller->>ChannelHub: _pushFunds(to, token, amount)
ChannelHub->>Token: transfer{gas: TRANSFER_GAS_LIMIT}(to, amount)
Token->>Token: Execute transfer to recipient
Token->>Token: Donate tokens back to ChannelHub
Token-->>ChannelHub: return true
ChannelHub-->>Caller: Transfer succeeds (no false reclaim)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
H-H01: fix(contracts): disallow challenge with CLOSE or FIN_MIG intents (#631) H-M01: fix(contracts/ChannelHub): remove transferred amount check (#636) fix(contracts/ChannelHub): allow unblocking escrow ops after migration (#637) M-C01: feat(contracts): add token check between states (#639) H-L03: fix(contracts/ChannelHub): skip disputed escrow during purge (#640) M-H04: feat(contracts/ChannelHub): purge during escrow challenge finalization, count both skip and purge (#641) M-M02: fix(contracts): require zero allocs on close (#643) M-C02: fix(rpc/core): apply finalize escrow deposit correctly (#644) H-L02: fix(contracts/ChannelHub): revert on withdrawFromVault failure (#651) M-H03: fix(clearnode): log correct fields on failure (#647) M-C03: fix(pkg/core): disallow negative amount transitions (#645) H-L01: fix(contracts/ChannelHub): add CH address to prevent val addition replay (#650) M-H06(clearnode): restrict max channel challenge duration (#654) M-M03(clearnode): revert EscrowLock on insufficient home user balance (#655) M-M04(clearnode): support default signer even if it is not approved (#656) M-M05(clearnode): require correct intent on FinalizeEscrowWithdrawal (#657) M-M09: reject asset decimals exceeding its token's decimals (#659) M-L01: return correct errors during state advancement validation (#660) M-L03: limit number of related ids per session key state (#662) M-I02: normalize input hex addresses (#663) M-H01: feat(contracts/ChannelHub): restrict to one node (#649) M-H07: fix(contracts/ChannelHub): emit stored, not arbitrary candidate state (#664) M-I01: feat(contracts/ChannelHub): remove updateLastState flag (#665) H-L06: fix(contracts/ChannelHub): remove payable from methods, clarify in create (#666) H-L09: feat(contracts/ChannelEngine): add non-home migration version check (#669) YNU-839: fix blockchain listener lifecycle (#658) M-H09: use channel signer for all channel state node sigs (#667) H-L07: fix(contracts/ChannelHub): restrict createChannel to non-existing channels (#668) H-I02: docs(contracts): add a note that rebasing tokens are not supported (#670) H-I03: fix(contracts/ChannelHub): use CIE pattern in depositToHub (#671) M-H11: revert empty signatures on quorum verification (#672) M-H11: reject issuance of receiver state during escrow ops (#674) H-I01: docs(contracts): note that fee-on-transfer tokens are not supported (#675) M-L04: docs: mention liquidity monitoring (#677) fix(contracts/ChannelHub): fix initialize escrow deposit dos (#679) M-H08: enforce strict transition ordering after MutualLock and EscrowLock (#680) fix: run forge fmt (#681) M-L05: docs(contracts): document native token deposits (#685) fix(clearnode): resolve a set of audit findings (#686) M-H13: feat(contract): add validateChallengeSignature, revert in SK validator (#688)
Description
The
_pushFundsfunction uses a balance-checking pattern to verify ERC20 transfer success by comparing balances before and after the transfer. However, whensuccess == truebut the balance check fails (balanceAfter != balanceBefore - amount), the protocol incorrectly credits the full amount to_reclaimseven though the tokens have already been transferred to the recipient. This creates a double-spend vulnerability where the recipient receives tokens twice: once from the actual transfer and once from claiming the reclaim. The vulnerability is triggered when the contract's balance increases during the transfer operation, which occurs with ERC777 tokens when the recipient's tokensReceived hook donates tokens back to ChannelHub. The protocol explicitly supports ERC777 tokens (evidenced by the L150 comment about 'ERC777 hooks' in gas limit calculation), making this an intended use case. The vulnerability affects all operations calling_pushFunds(...):withdrawFromVault(..),closeChannel(..),finalizeEscrowDeposit(..),finalizeEscrowWithdrawal(..), and challenge processing.Impact
A malicious node operator can deploy a contract with a malicious tokensReceived hook that donates tokens back to ChannelHub during withdrawals. This causes the balance check to fail even though the transfer succeeded, resulting in the full amount being credited to reclaims. The attacker can then claim these funds again via claimFunds, effectively receiving 2x the withdrawn amount while only 1x is deducted from their vault accounting. By repeating this attack, the attacker can drain their entire vault balance plus an equal amount from the protocol's reserves, creating an insolvency situation where other users cannot withdraw their full balances. The cost is minimal (1-2 wei per withdrawal) and the profit is 100% of the withdrawn amount. Since the ChannelHub holds tokens for all nodes in a shared balance, the accounting shortfall created by this exploit affects all other users of the same token, effectively allowing the attacker to steal funds that belong to other nodes.
Summary by CodeRabbit
Bug Fixes
Documentation