#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_fee
will 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_account
balance, thefee
is divided bysettlement_token_price
to 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_fee
will 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
settlement
to close a position in a market.Call
_settlement_fee_payments
processes the fee. After settling PnL and funding, the user'spending_token_balance
is100 * 10^8
, and thesettlement_token_price
is 1.001.Based on
trade_size
,trade_price
, andfee_rates
, the fee is calculated to be10^8
in 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_account
balance is increased byfee_in_settlement_token = 10^8 / 1.001 ≈ 0.999 * 10^8
.The user is overcharged by approximately
10^6
USDC, and this excess fee is not credited to thefee_account
, resulting in the funds being permanently locked.
Was this helpful?