Skip to content

H-L03: fix(contracts/ChannelHub): skip disputed escrow during purge#640

Merged
nksazonov merged 2 commits into
fix/audit-findingsfrom
fix/audit-h-l03
Apr 1, 2026
Merged

H-L03: fix(contracts/ChannelHub): skip disputed escrow during purge#640
nksazonov merged 2 commits into
fix/audit-findingsfrom
fix/audit-h-l03

Conversation

@nksazonov
Copy link
Copy Markdown
Contributor

@nksazonov nksazonov commented Mar 31, 2026

Description

The _purgeEscrowDeposits(...) function breaks immediately when it encounters a DISPUTED escrow (L408), preventing all subsequent escrows from being processed. Unlike FINALIZE escrows which are skipped with continue (L392), DISPUTED escrows cause the loop to exit entirely. This leaves the escrowHead pointer stuck before the DISPUTED escrow, blocking all subsequent escrows from being purged. The purge mechanism doesn't check challengeExpireAt or automatically handle expired disputes, requiring manual intervention via finalizeEscrowDeposit to unblock the queue.

Impact

Node liquidity is locked in all escrows queued after a DISPUTED escrow until manual intervention occurs. The automated purge mechanism fails completely when DISPUTED escrows exist, even after their challenge period expires. This requires off-chain monitoring to detect and resolve, breaking the protocol's automated design. The minimum blockage period is 1 day (CHALLENGE_DURATION), but could extend indefinitely if not actively monitored. While the node can self-resolve by calling finalizeEscrowDeposit, this represents an operational failure of the intended automated system.

Summary by CodeRabbit

Release Notes

  • New Features

    • Made escrow deposit statistics function publicly accessible, enabling queries for unlockable deposit counts and aggregated amounts across the deposit queue.
  • Bug Fixes

    • Enhanced the escrow deposit purge mechanism to correctly identify and skip both finalized and disputed deposits during queue iteration, preventing them from unnecessarily blocking subsequent purge operations.
  • Tests

    • Added comprehensive test coverage for escrow deposit statistics queries and purge operations across various queue configurations and edge cases.

@nksazonov nksazonov changed the base branch from main to fix/audit-findings March 31, 2026 15:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8cd42f3a-b8bf-4407-95bc-2d2cda7137af

📥 Commits

Reviewing files that changed from the base of the PR and between 1ba20be and c77bf74.

📒 Files selected for processing (5)
  • contracts/src/ChannelHub.sol
  • contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_EscrowDepositPurge_Base.t.sol
  • contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_getUnlockableEscrowDepositStats.t.sol
  • contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_purgeEscrowDeposits.t.sol
  • contracts/test/TestChannelHub.sol

📝 Walkthrough

Walkthrough

Modified ChannelHub's escrow deposit purge and stats logic to make getUnlockableEscrowDepositStats() public and introduce a _isEscrowDepositSkippable() predicate that treats both FINALIZED and DISPUTED escrows as skippable during queue iteration, alongside comprehensive test infrastructure and coverage.

Changes

Cohort / File(s) Summary
Core Logic Refactor
contracts/src/ChannelHub.sol
Made getUnlockableEscrowDepositStats() public; added _isEscrowDepositSkippable() predicate to skip both FINALIZED and DISPUTED escrows during stats computation and purge operations; updated loop logic in _purgeEscrowDeposits() to use the new predicate.
Test Harness
contracts/test/TestChannelHub.sol
Added external test-harness functions: workaround_setEscrowDeposit(), workaround_addEscrowDepositId(), harness_escrowDepositIds(), and harness_purgeEscrowDeposits() to enable direct manipulation and inspection of escrow state in tests.
Test Base Contract
contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_EscrowDepositPurge_Base.t.sol
Created abstract base test contract with TestChannelHub and MockERC20 initialization, deterministic address setup, and internal helper methods to create escrows in specific states (INITIALIZED unlockable/not-yet-unlockable, FINALIZED, DISPUTED).
Stats Tests
contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_getUnlockableEscrowDepositStats.t.sol
Comprehensive test suite for getUnlockableEscrowDepositStats() verifying empty queue returns zero, single/multiple entries are correctly counted and aggregated, FINALIZED/DISPUTED entries are skipped, iteration stops at non-unlockable entries, and stats computation advances from updated escrow head.
Purge Tests
contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_purgeEscrowDeposits.t.sol
Extensive test coverage for purgeEscrowDeposits() behavior: empty queue handling, single-entry purge/skip scenarios, mixed queue skipping of FINALIZED/DISPUTED with subsequent purging of unlockable entries, maxToPurge limiting, multi-node/token accounting correctness, and proper event emissions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • ihsraham
  • philanton
  • dimast-x

Poem

🐰 Disputed, finalized—both skip the queue today,
Stats count only unlockable, no delay,
Public now the stats that once hid away,
With tests galore to show the proper way! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'skip disputed escrow during purge' accurately reflects the main change: fixing the purge function to skip disputed escrow deposits instead of breaking the loop, allowing subsequent escrows to be processed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-h-l03

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nksazonov
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nksazonov nksazonov merged commit b4c6077 into fix/audit-findings Apr 1, 2026
8 checks passed
@nksazonov nksazonov deleted the fix/audit-h-l03 branch April 1, 2026 07:51
nksazonov added a commit that referenced this pull request Apr 16, 2026
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants