M-M02: fix(contracts): require zero allocs on close#643
Conversation
📝 WalkthroughWalkthroughThe pull request refactors channel closure validation and fund handling in the ChannelEngine and ChannelHub contracts. It replaces the previous allocation sum validation with a strict requirement that both user and node allocations must be zero during channel closure, and removes special-case fund-pushing logic from the state transition handler to rely on standard delta processing. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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 (1)
contracts/test/ChannelEngine/ChannelEngine_validateTransition.t.sol (1)
51-61: Good complementary test for node allocation.This correctly validates that even when
userAllocation == 0passes the first check, a non-zeronodeAllocationstill triggers the revert. The net flow setup (allocsSum == netFlowsSum) ensures the universal validation passes, isolating the CLOSE-specific check.Consider adding a happy-path test that validates a successful CLOSE with zero allocations and appropriate negative deltas, ensuring the full transition completes without revert:
💡 Optional: Add happy-path test
function test_close_success_zeroAllocations() public { ChannelEngine.TransitionContext memory ctx = _operatingCtx(1000); // User withdraws all funds: userAllocation -> 0, userNetFlow -> 0 // userNfDelta = 0 - 1000 = -1000 (push 1000 to user) State memory candidate = TestUtils.nextState(ctx.prevState, StateIntent.CLOSE, [uint256(0), uint256(0)], [int256(0), int256(0)]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); assertEq(effects.userFundsDelta, -1000); assertEq(effects.nodeFundsDelta, 0); assertTrue(effects.closeChannel); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@contracts/test/ChannelEngine/ChannelEngine_validateTransition.t.sol` around lines 51 - 61, Add a happy-path unit test named e.g. test_close_success_zeroAllocations that uses TransitionContext ctx = _operatingCtx(1000), builds a candidate State via TestUtils.nextState with StateIntent.CLOSE and zero allocations/deltas (both user and node zero), calls ChannelEngine.validateTransition(ctx, candidate) to get ChannelEngine.TransitionEffects, and asserts effects.userFundsDelta equals -1000, effects.nodeFundsDelta equals 0, and effects.closeChannel is true; reference the existing helpers TransitionContext, TestUtils.nextState, ChannelEngine.validateTransition, and the TransitionEffects fields to locate where to add the test.
🤖 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/test/ChannelEngine/ChannelEngine_validateTransition.t.sol`:
- Around line 51-61: Add a happy-path unit test named e.g.
test_close_success_zeroAllocations that uses TransitionContext ctx =
_operatingCtx(1000), builds a candidate State via TestUtils.nextState with
StateIntent.CLOSE and zero allocations/deltas (both user and node zero), calls
ChannelEngine.validateTransition(ctx, candidate) to get
ChannelEngine.TransitionEffects, and asserts effects.userFundsDelta equals
-1000, effects.nodeFundsDelta equals 0, and effects.closeChannel is true;
reference the existing helpers TransitionContext, TestUtils.nextState,
ChannelEngine.validateTransition, and the TransitionEffects fields to locate
where to add the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9eef6887-e8d3-4958-9d6c-acccc081e0d2
📒 Files selected for processing (4)
contracts/src/ChannelEngine.solcontracts/src/ChannelHub.solcontracts/test/ChannelEngine/ChannelEngine_validateTransition.t.solprotocol-description.md
💤 Files with no reviewable changes (1)
- contracts/src/ChannelHub.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
In cooperative path in
closeChannel, the contract validates the submittedCLOSEstate, computes transition effects, applies them, and then zeroesmeta.lockedFunds. However, the engine does not enforce that the final allocations are actually paid out before the channel is deleted._calculateCloseEffectsonly checks thatuserAllocation + nodeAllocation <= lockedFunds, that the resultingfinalLockedFundsis non-negative, and that it is large enough for the specialnodeAllocationhandling.In ChannelHub, cooperative close path does not automatically pay out the final allocations shown in the
CLOSEstate. The user only receives funds ifuserFundsDelta < 0. Consequently, if the signedCLOSEstate keeps the same net flows as the previous state, both deltas can be zero even though the state still shows positive allocations. In that case, no funds are transferred during close, but_applyEffectsstill setsmeta.lockedFundsto zero. The channel is removed, while the tokens remain stuck in the contract.Summary by CodeRabbit
Bug Fixes
Documentation
Tests