#47295 [SC-Insight] Configurator Can Manipulate Critical Parameters to Force Mass Liquidations and Drain Protocol Funds
Submitted on Jun 12th 2025 at 11:33:20 UTC by @Catchme for IOP | Paradex
Report ID: #47295
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/tradeparadex/audit-competition-may-2025/tree/main/paraclear
Impacts:
Protocol insolvency
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
The Paraclear protocol grants the CONFIGURATOR_ROLE
unrestricted power to modify critical financial parameters including margin requirements, liquidation fees, and trading fees without any upper bounds validation. A malicious configurator can exploit this to set extreme parameter values (e.g., 1000% margin requirements, 100% liquidation fees) that instantly make all user positions unhealthy, then liquidate them through the insurance fund to extract maximum penalties and trading fees, effectively draining the entire protocol.
Vulnerability Details
1. Unrestricted Parameter Control
The configurator role has unlimited power to modify critical parameters with no upper bounds:
Liquidation Fee - No Upper Limit:
// paraclear/src/paraclear/paraclear.cairo:427-430
fn setLiquidationFee(ref self: ContractState, fee: felt252) {
self._assert_only_configurator();
self.Paraclear_liquidation_fee.write(fee); // No validation whatsoever!
}
Margin Parameters - No Upper Limit:
// paraclear/src/perpetual/future.cairo:154-167
fn update_perpetual_asset(ref self: ComponentState<TContractState>, new_asset: PerpetualAsset) {
self.assert_only_role(roles::CONFIGURATOR_ROLE);
assert!(new_asset.market != 0, "Synthetic: Can't update asset with unset market");
// No validation on margin_params.imf_base, imf_factor, mmf_factor
self.Paraclear_perpetual_asset.write(new_asset.market, new_asset);
}
Trading Fees - No Upper Limit:
// paraclear/src/account/account.cairo:290-295
fn set_global_fee_rate(...) {
let net_fee = maker_fee + taker_fee;
assert!(
net_fee.try_into().unwrap() >= 0_i128,
"Negative total fee rate is not allowed" // Only checks non-negative!
);
}
2. Margin Calculation Impact
The margin requirement calculation directly uses these unvalidated parameters:
// paraclear/src/perpetual/future.cairo:792-804
fn future_margin_requirement(
self: @PerpetualAssetBalance,
imf_base: i128, // Directly used without bounds checking
mmf_factor: i128,
margin_check_type: felt252,
mark_price: i128,
) -> i128 {
let current_value_abs = abs_128(self.get_value(mark_price));
let margin_fraction = self.future_margin_fraction(imf_base, mmf_factor, margin_check_type);
mul_128(current_value_abs, margin_fraction) // Can result in extreme values
}
3. Health Check Vulnerability
Account health is determined by comparing account value to margin requirements:
// paraclear/src/account/account.cairo:612-616
fn excess_balance(self: @AccountState, margin_check_type: felt252) -> i128 {
self.account_value() - self.margin_requirement(margin_check_type)
}
When imf_base
is set to extreme values (e.g., 10x normal), all accounts become immediately liquidatable.
4. Liquidation Penalty Extraction
The liquidation penalty is calculated as:
// paraclear/src/paraclear/paraclear.cairo:1386-1387
let liquidation_fee = self.getLiquidateFee();
let liq_penalty_full = mul_128(margin_requirement, liquidation_fee.try_into().unwrap());
With extreme parameters:
margin_requirement
= position_value × 1000% = 10× position valueliquidation_fee
= 100%liq_penalty_full
= 10× position value
5. Contradicts Official Documentation
The official Paradex documentation states:
"The Liquidation Fee is set to 70%"
"Partial Liquidation of unhealthy accounts and attempts to minimise impact on the user's assets"
However, the code allows unlimited fee manipulation, violating these safety guarantees.
Impact Details
Parameter Manipulation:
Set
imf_base
to 1000% (normal: 1-5%)Set liquidation fee to 100% (documented: 70%)
Set trading fees to 50% (normal: 0.1%)
Mass Liquidation Trigger:
All user positions instantly become unhealthy (excess_balance < 0)
Insurance fund can liquidate any account
Profit Extraction:
For a user with $10,000 position: - New margin requirement: $10,000 × 1000% = $100,000 - Liquidation penalty: $100,000 × 100% = $100,000 - Additional trading fees during settlement: $10,000 × 50% = $5,000
References
Proof of Concept
Proof of Concept
PoC
paraclear/src/paraclear/tests/test_paraclear_liquidations.cairo
use crate::tests::test_utils::{
ADMIN, CONFIGURATOR, EXECUTOR, INSURANCE_FUND, NO_ROLE_ADDRESS, STATE_PUBLISHER,
setup_paraclear, setup_paraclear_with_oracle,
};
#[test]
fn test_poc_configurator_manipulates_critical_parameters() {
let (_, paraclear_dispatcher) = setup_paraclear();
// Check initial liquidation fee (should be 0 in test setup)
let initial_fee = paraclear_dispatcher.getLiquidateFee();
assert(initial_fee == 0, 'fee should be 0');
start_cheat_caller_address(paraclear_dispatcher.contract_address, CONFIGURATOR());
// 1. Set extreme liquidation fee (1000% instead of documented 70%)
// Documentation claims "The Liquidation Fee is set to 70%" but code has NO validation
let extreme_liquidation_fee = 1000000000; // 1000% in 8-decimal format
paraclear_dispatcher.setLiquidationFee(extreme_liquidation_fee);
// 2. Test even more extreme values to show lack of bounds checking
let absurd_fee = 50000000000; // 50000% - completely unreasonable
paraclear_dispatcher.setLiquidationFee(absurd_fee);
stop_cheat_caller_address(paraclear_dispatcher.contract_address);
// 3. Verify extreme parameters were stored without any validation
let final_fee = paraclear_dispatcher.getLiquidateFee();
assert(final_fee == absurd_fee, 'fee set without bounds checking');
// Impact: With 50000% liquidation fee, any liquidation would extract 500x more
// than the normal fee, enabling complete protocol fund drainage
// This completely violates the documented guarantee of 70% max fee
}
Was this helpful?