#46639 [SC-Low] The `_settlement_fee_payments` function contains a calculation error that leads to abnormal user balances.
Submitted on Jun 2nd 2025 at 18:31:46 UTC by @shaflow1 for IOP | Paradex
Report ID: #46639
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/tradeparadex/audit-competition-may-2025/tree/main/paraclear
Impacts:
Permanent freezing of funds
Description
Brief/Intro
The _settlement_fee_payments function contains an error in the calculation of balance_after_fee because the fee is not normalized from value terms to the settlement token (USDC). This leads to incorrect user balance calculations when the settlement_token_price is not equal to 1.
Vulnerability Details
fn _settlement_fee_payments(
ref self: ContractState,
account: ContractAddress,
account_state: @AccountState,
pending_token_balance: i128,
token_balance_address: ContractAddress,
trade_size: i128,
trade_price: i128,
settlement_token_price: NonZero<i128>,
asset: @PerpetualMarketAsset,
) -> i128 {
let fee_account = self.getFeeAccount();
let base_fee = asset
.calculate_fee(
*account_state.asset_data,
trade_size,
trade_price,
false,
account_state.fee_rates,
);
let (fee, referrer, fee_commission) = account_state
.get_trade_fee_and_referral_commission(base_fee);
let balance_after_fee = pending_token_balance - fee;
self
.token
.write_asset_balance(account, token_balance_address, balance_after_fee.into());In the _settlement_fee_payments function, pending_token_balance represents the amount of settlement tokens (USDC) the user should hold after settling funding and PnL, and it is denominated in USDC with 8 decimals.
However, the fee is calculated using trade_price and trade_size, and its unit is in value terms (i.e., not yet normalized to USDC).
These two values (pending_token_balance and fee) cannot be directly subtracted. The fee must first be divided by the settlement_token_price to convert it into settlement token units (USDC) before it can be subtracted from pending_token_balance.
Failing to do this results in an incorrect calculation of balance_after_fee, which is then incorrectly written to the user's balance.
Impact Details
The direction of the impact depends on the settlement_token_price:
If
settlement_token_price> 1, thenbalance_after_feewill be underestimated, resulting in excessive fees being deducted from the user. Moreover, the overcharged portion is not credited to thefee_account, because later, when updating thefee_accountbalance, thefeeis divided bysettlement_token_priceto convert it to USDC. As a result, this extra deducted amount is effectively lost and permanently locked in the system.If
settlement_token_price< 1, thenbalance_after_feewill be overestimated, meaning the user pays less fee than intended. However, the full fee amount (in USDC terms) is still added to thefee_account, leading to the total USDC balance recorded in the system exceeding the actual amount of USDC held by the contract. This can potentially cause withdrawal failures later, as the system assumes it holds more USDC than it actually does.
Since this deviation occurs every time the settlement function is called — unless settlement_token_price is exactly 1 — it will accumulate over time. In practice, the price of USDC is usually slightly below 1, so the system tends to over-credit users, which makes the risk of withdrawal failures even more likely. During periods when USDC depegs, the issue becomes even more pronounced and dangerous.
References
https://github.com/tradeparadex/audit-competition-may-2025/blob/0eb81b26a67666c399b4e16b39a96c19848ab7fd/paraclear/src/paraclear/paraclear.cairo#L1991
Proof of Concept
Proof of Concept
Every time a settlement occurs with settlement_token_price not equal to 1, balance_after_fee cannot be accurately calculated:
The user calls
settlementto close a position in a market.Call
_settlement_fee_paymentsprocesses the fee. After settling PnL and funding, the user'spending_token_balanceis100 * 10^8, and thesettlement_token_priceis 1.001.Based on
trade_size,trade_price, andfee_rates, the fee is calculated to be10^8in value units. There is no referral fee.The function calculates
balance_after_fee = 100 * 10^8 - 10^8 = 99 * 10^8, and the user's USDC balance is updated to99 * 10^8.The
fee_accountbalance is increased byfee_in_settlement_token = 10^8 / 1.001 ≈ 0.999 * 10^8.The user is overcharged by approximately
10^6USDC, and this excess fee is not credited to thefee_account, resulting in the funds being permanently locked.
Was this helpful?