M-H04: feat(contracts/ChannelHub): purge during escrow challenge finalization, count both skip and purge#641
Conversation
…n, count both skip and purge
…n, count both skip and purge
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe pull request redefines the escrow deposit purge queue constraint from Changes
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.
|
There was a problem hiding this comment.
🧹 Nitpick comments (2)
contracts/test/ChannelHub_allFlowsInvokePurge.t.sol (1)
24-62: Please add migration paths to this suite.
initiateMigration()andfinalizeMigration()still reach_purgeEscrowDeposits()via_applyEffects()incontracts/src/ChannelHub.sol, so they are part of the same regression surface. Right now a future change could stop invoking purge there without tripping this “all flows” suite.Also applies to: 284-567
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@contracts/test/ChannelHub_allFlowsInvokePurge.t.sol` around lines 24 - 62, The test suite is missing migration flows that also trigger _purgeEscrowDeposits via _applyEffects; add cases that call initiateMigration() and finalizeMigration() (both home and non-home variants if applicable) to the ChannelHub_allFlowsInvokePurge.t.sol tests so the sentinel injection (_snapshotAndInjectSentinel()) is applied before each migration action and _assertPurgeInvoked() is asserted afterward, ensuring initiateMigration and finalizeMigration are covered alongside the other flows that must invoke _purgeEscrowDeposits in ChannelHub.sol.contracts/src/ChannelHub.sol (1)
386-395: Align the exposed terminology with the new step budget.This loop now budgets inspected entries, not successful purges. Renaming/documenting the remaining exposed
maxToPurgesurface asmaxStepswould make the new behavior much clearer for callers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@contracts/src/ChannelHub.sol` around lines 386 - 395, The public-facing semantics changed to budget inspected entries rather than successful purges, so update the exposed parameter and any related references from the old name (e.g., maxToPurge) to maxSteps and adjust documentation/comments accordingly; specifically, in the _purgeEscrowDeposits function signature and any external callers/events/ABI docs that reference maxToPurge, rename the parameter to maxSteps (or add a clear comment that maxToPurge is now a step budget) and ensure all mentions (function name _purgeEscrowDeposits, escrowHead, _escrowDepositIds usage) reflect the new terminology to avoid confusion for callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@contracts/src/ChannelHub.sol`:
- Around line 386-395: The public-facing semantics changed to budget inspected
entries rather than successful purges, so update the exposed parameter and any
related references from the old name (e.g., maxToPurge) to maxSteps and adjust
documentation/comments accordingly; specifically, in the _purgeEscrowDeposits
function signature and any external callers/events/ABI docs that reference
maxToPurge, rename the parameter to maxSteps (or add a clear comment that
maxToPurge is now a step budget) and ensure all mentions (function name
_purgeEscrowDeposits, escrowHead, _escrowDepositIds usage) reflect the new
terminology to avoid confusion for callers.
In `@contracts/test/ChannelHub_allFlowsInvokePurge.t.sol`:
- Around line 24-62: The test suite is missing migration flows that also trigger
_purgeEscrowDeposits via _applyEffects; add cases that call initiateMigration()
and finalizeMigration() (both home and non-home variants if applicable) to the
ChannelHub_allFlowsInvokePurge.t.sol tests so the sentinel injection
(_snapshotAndInjectSentinel()) is applied before each migration action and
_assertPurgeInvoked() is asserted afterward, ensuring initiateMigration and
finalizeMigration are covered alongside the other flows that must invoke
_purgeEscrowDeposits in ChannelHub.sol.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8b687ec9-1fb7-41da-96c3-310689d40040
📒 Files selected for processing (4)
contracts/src/ChannelHub.solcontracts/test/ChannelHub_allFlowsInvokePurge.t.solcontracts/test/ChannelHub_escrowDepositPurge/ChannelHub_purgeEscrowDeposits.t.solcontracts/test/TestChannelHub.sol
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 deposit escrow purge logic is intended to be bounded by
MAX_DEPOSIT_ESCROW_PURGE, but the implementation only bounds the number of escrows that are successfully purged, not the number of queue entries scanned. When the loop encounters an alreadyFINALIZEDescrow, it advances without increasingpurgedCount. As a result, a single purge call can scan an arbitrarily large finalized prefix even whenmaxToPurgeis small, making the actual work performed by the call unbounded.This becomes exploitable because escrow deposits are stored in one global queue. The timeout finalization path marks disputed escrows as
FINALIZEDwithout advancingescrowHead, allowing finalized entries to accumulate in front of the queue until a later purge attempt tries to process them.An attacker can create many small non-home-chain escrow deposits and cause a large finalized prefix to accumulate at the queue head. Once that happens, the next call to
_purgeEscrowDepositsmay need to iterate through all of those finalized entries in a single transaction before it can make progress. If the prefix is large enough, the purge step can run out of gas and revert.Because
_purgeEscrowDepositsis invoked by the protocol’s internal paths, this can cause most normal channel, escrow, and migration transitions to revert.As a result, liquidity that should have been released can become practically stuck and core protocol workflows can become unavailable.
Summary by CodeRabbit
Release Notes
Bug Fixes
Tests