Skip to content

Fix permanent fund lock in pending slashes#27

Open
paulee wants to merge 1 commit intoentrius:testfrom
paulee:fix/rescue-pending-slash
Open

Fix permanent fund lock in pending slashes#27
paulee wants to merge 1 commit intoentrius:testfrom
paulee:fix/rescue-pending-slash

Conversation

@paulee
Copy link
Copy Markdown

@paulee paulee commented Apr 9, 2026

Problem

During a security audit of the ink! smart contract, I found a critical fund-lock vulnerability in the pending slash mechanism.

Flow that locks funds permanently:

  1. timeout_swap slashes miner collateral and calls transfer(swap.user, actual_slash)
  2. If the transfer fails (e.g. user account reaped below existential deposit), funds go to pending_slashes mapping and swap data is deleted (line 839)
  3. User calls claim_slash — but if their account is destroyed/reaped, the transfer fails again. The function re-inserts the pending slash (line 916-918)
  4. No recovery path exists — funds are permanently locked in the contract

On Substrate chains, accounts are reaped when balance drops below existential deposit. Once reaped, all subsequent transfers to that account fail, making claim_slash a dead end.

Fix

Added rescue_pending_slash — an owner-callable method that redirects stuck funds to an alternative recipient, breaking the permanent lock cycle.

#[ink(message)]
pub fn rescue_pending_slash(&mut self, swap_id: u64, recipient: AccountId) -> Result<(), Error>
  • Only contract owner can call (access controlled via ensure_owner())
  • Removes the pending slash entry and transfers funds to the specified recipient
  • If the rescue transfer itself fails, re-inserts the pending slash (no fund loss)
  • Emits SlashClaimed event for tracking

Tests

Added 8 unit tests covering the full slash lifecycle:

  • test_timeout_slash_transfer_succeeds — happy path timeout with successful transfer
  • test_timeout_slash_depletes_collateral_deactivates_miner — collateral fully depleted
  • test_claim_slash_pays_out — user successfully claims pending slash
  • test_claim_slash_wrong_user_rejected — wrong caller can't claim
  • test_claim_slash_nonexistent — claim on non-existent slash fails
  • test_owner_rescue_pending_slash — owner redirects stuck funds
  • test_rescue_pending_slash_not_owner — non-owner can't rescue
  • test_rescue_nonexistent — rescue on non-existent slash fails

Test plan

  • cargo test — all 8 tests pass
  • Verify on testnet that rescue_pending_slash correctly transfers funds to alternative recipient
  • Confirm only contract owner can call rescue_pending_slash

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.

1 participant