Skip to content

fix(order-handler): charge liquidation keeper fee to the trader, not the pool - #705

Merged
IbrahimIjai merged 2 commits into
SO4-Markets:mainfrom
dev-debbie-umoh:fix/540-liquidation-keeper-fee-collateral-writeback
Aug 31, 2026
Merged

fix(order-handler): charge liquidation keeper fee to the trader, not the pool#705
IbrahimIjai merged 2 commits into
SO4-Markets:mainfrom
dev-debbie-umoh:fix/540-liquidation-keeper-fee-collateral-writeback

Conversation

@dev-debbie-umoh

@dev-debbie-umoh dev-debbie-umoh commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

order_handler::liquidate_position pays the liquidation keeper's execution fee by withdrawing real tokens directly from the market pool, and (per the earlier #629 fix) correctly debits data_store's pool_amount ledger to match that real withdrawal. But the in-memory position read at the top of the function was never written back to storage with the fee deducted. decrease_position, called immediately after, re-reads position.collateral_amount fresh from storage — full and un-reduced — and computes the trader's payout as if the fee had never been taken.

Net effect: the fee's real token outflow is correctly tracked in pool_amount (so there's no ledger-vs-real-balance drift in the narrow sense #629 fixed), but that debit isn't backed by any corresponding reduction in what the trader is owed. Every fee-bearing liquidation quietly transfers value from LPs (pool_amount absorbs the fee) to nowhere in particular — the trader still receives their full, un-reduced payout, and the pool is out the fee amount with no offsetting benefit.

Fix

Write position.collateral_amount -= fee_to_transfer back to storage before calling decrease_position, so the fee is deducted from the trader's own collateral — decrease_position's own collateral_sum/output_amount computation then correctly reflects it, and the trader (not the pool) bears the cost.

Closes #540

Test plan

  • cargo test -p order-handler --lib — 64 passed, 0 failed (3 unrelated pre-existing failures on unmodified main too: custom_heartbeat_timeout_is_respected, execute_order_records_keeper_heartbeat, keeper_goes_stale_after_timeout_and_role_is_revocable — oracle price-lookup issue unrelated to this change, verified independently)
  • New test liquidate_position_keeper_fee_ledger_matches_real_balance: compares an otherwise-identical liquidation with vs. without a keeper fee configured. Verified this test fails without the fix (combined ledger differs from the real balance by exactly the fee) and passes with it (both the combined ledger pool_amount + collateral_sum and the pool's real token balance are unaffected by whether a fee is configured — only who gets paid changes).

dev-debbie-umoh and others added 2 commits August 31, 2026 17:38
…the pool (Closes SO4-Markets#540)

liquidate_position pays the liquidation keeper's execution fee by
withdrawing real tokens from the market pool via withdraw_from_pool,
and (per the earlier SO4-Markets#629 fix) debits data_store's pool_amount ledger
to match. But the in-memory `position` read at the top of the function
was never written back to storage with the fee deducted, so the
subsequent decrease_position call re-reads the full, un-reduced
collateral_amount from storage and computes the trader's payout as if
the fee had never been taken.

Net effect: the fee's real token outflow is correctly tracked in
pool_amount, but that ledger debit isn't backed by any reduction in
what the trader is owed — the pool_amount debit is effectively an
uncompensated loss to LPs on every fee-bearing liquidation, while the
trader receives exactly what they'd get if the fee were zero.

Fix: write `position.collateral_amount -= fee_to_transfer` back to
storage before calling decrease_position, so the fee is deducted from
the trader's own collateral (via decrease_position's own
collateral_sum/output_amount computation) rather than absorbed by the
pool. Verified with a before/after comparison (fee vs. no fee): the
combined ledger (pool_amount + collateral_sum) and the pool's real
token balance are now both unaffected by whether a fee is configured —
the fee only ever changes who gets paid, never what leaves the pool or
how the pool's accounting reads.
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@dev-debbie-umoh Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@IbrahimIjai
IbrahimIjai merged commit d011f24 into SO4-Markets:main Aug 31, 2026
0 of 3 checks passed
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.

order_handler: liquidation keeper fee leaves the pool with no data_store ledger debit, silently inflating pool_amount vs real balance

2 participants