Mammoth Mocha Koala
Medium
The Double Subtraction of Fees issue occurs when fees already accounted for in the previous checkpoint’s collateral balance are subtracted again when computing the next checkpoint’s collateral. This results in fees being deducted twice, artificially reducing the collateral balance.
library CheckpointLib { /// @notice Accumulate pnl and fees from the latest position to next position /// @param order The next order /// @param fromVersion The previous latest version /// @param toVersion The next latest version /// @return next The next checkpoint /// @return response The accumulated pnl and fees function accumulate( IMarket.Context memory context, IMarket.SettlementContext memory settlementContext, uint256 orderId, Order memory order, Guarantee memory guarantee, Version memory fromVersion, Version memory toVersion ) external returns (Checkpoint memory next, CheckpointAccumulationResponse memory) { CheckpointAccumulationResult memory result;
// accumulate
result.collateral = _accumulateCollateral(context.latestPositionLocal, fromVersion, toVersion);
result.priceOverride = _accumulatePriceOverride(guarantee, toVersion);
(result.tradeFee, result.subtractiveFee, result.solverFee) = _accumulateFee(order, guarantee, toVersion);
result.offset = _accumulateOffset(order, guarantee, toVersion);
result.settlementFee = _accumulateSettlementFee(order, guarantee, toVersion);
result.liquidationFee = _accumulateLiquidationFee(order, toVersion);
// update checkpoint
next.collateral = settlementContext.latestCheckpoint.collateral
.sub(settlementContext.latestCheckpoint.tradeFee) // trade fee processed post settlement
.sub(Fixed6Lib.from(settlementContext.latestCheckpoint.settlementFee)); // settlement / liquidation fee processed post settlement
next.collateral = next.collateral
.add(settlementContext.latestCheckpoint.transfer) // deposit / withdrawal processed post settlement
.add(result.collateral) // incorporate collateral change at this settlement
.add(result.priceOverride); // incorporate price override pnl at this settlement
next.transfer = order.collateral;
next.tradeFee = Fixed6Lib.from(result.tradeFee).add(result.offset);
next.settlementFee = result.settlementFee.add(result.liquidationFee);
emit IMarket.AccountPositionProcessed(context.account, orderId, order, result);
return (next, _response(result));
}
Each checkpoint’s collateral value is intended to reflect the net balance after all prior fees (e.g., tradeFee, settlementFee) have been applied.
For example, if the previous checkpoint’s collateral is 100 and a tradeFee of 10 was applied, the collateral in that checkpoint should already be 90 (i.e., 100 - 10).
next.collateral = settlementContext.latestCheckpoint.collateral .sub(settlementContext.latestCheckpoint.tradeFee) // ❌ Subtracts fees again .sub(Fixed6Lib.from(settlementContext.latestCheckpoint.settlementFee)); // ❌ Here, the code subtracts the previous checkpoint’s tradeFee and settlementFee from latestCheckpoint.collateral, which already includes these deductions from prior computations. This double-counts the fees.
Example Initial State:
latestCheckpoint.collateral = 100
latestCheckpoint.tradeFee = 10 (already deducted to reach 100).
Erroneous Calculation:
next.collateral = 100 (previous collateral) - 10 (tradeFee) = 90.
Result: Collateral becomes 90, but it should remain 100 (since the 10 fee was already subtracted in the prior checkpoint).
Impact:
Collateral is reduced by 10 again, leading to an incorrect balance of 90 instead of 100.
Over time, this error compounds, disproportionately penalizing users by over-deducting fees.
No response
No response
No response
Over time, this error compounds, disproportionately penalizing users by over-deducting fees.
No response
Remove the redundant subtraction of prior fees when computing next.collateral: