# If cross positions use the same margin token as collateral and close without liability, then fee accounting will be completely wrong

Cross-margin positions closing at a loss with same-token collateral send full PnL+fees from portfolio vault to stakeToken while fees stay unsettled, causing double-counted fees and vault insolvency.

- Payout: Contest reward
- Severity: high
- Program: Elfi Protocol
- Credited: mstpr-brainbot
- Disclosed: 2024-05-09
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2024-05-elfi-protocol-judging/issues/60
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-05-elfi-protocol%3AH-13
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:precision-loss

**Target:** DecreasePositionProcess / FeeProcess / MarketProcess in elfi-perp-contracts
**Root cause:** When a cross position's margin token equals the collateral token and closes at a loss, the entire recordPnlToken (fees+pnl) is transferred from portfolio vault to stakeToken with zero liability, but the fees were already recorded as unsettled; the later updateMarketFundingFee call passes needUpdateUnsettle=false for cross positions, so the unsettled amount is never decremented.
**Steps:**
1. Open a cross-margin LONG on btcUsd with marginToken=wbtc and deposit wbtc collateral so cross balance covers the loss.
2. Mine blocks to accrue borrowing/funding fees.
3. Close the position at a loss via DECREASE order with a lower oracle price.
4. Observe decreasePosition: updateBorrowingFee/updateFundingFee add unsettled amounts (needUpdateUnsettle=true).
5. chargeTradingFee/chargeBorrowingFee add unsettled fees for cross positions.
6. subTokenWithLiability returns addLiability=0 since cross balance covers loss; transferOut sends full recordPnlToken from portfolioVault to stakeToken.
7. updateMarketFundingFee is called with !isCrossMargin=false, so settledFundingFee is not subtracted from unsettled.
8. Result: fees sit in stakeToken but remain unsettled; later settlement pulls them again from portfolio vault, double-counting.
**How found:** Code reading of decreasePosition flow: noticed updateFundingFee passes needUpdateUnsettle=true on entry but the later updateMarketFundingFee passes !isCrossMargin (false for cross), creating asymmetry; combined with the loss branch that transfers full recordPnlToken when addLiability=0.
**Exploitation:** Attacker opens and closes cross-margin positions at a loss with margin token equal to collateral, repeatedly forcing portfolio vault to pay out fees+pnl to stakeToken while fees remain unsettled, draining the portfolio vault and making it insolvent.
**Insight:** In perp DEX accounting, audit the symmetry of unsettled-fee bookkeeping: any path that adds to unsettled must have a matching decrement on settlement. Check branches where a loss transfer bypasses liability creation while fees were pre-recorded as unsettled.
