#47257 [SC-Insight] Lack of position quantity limit for a single account.
Submitted on Jun 11th 2025 at 15:10:01 UTC by @shaflow1 for IOP | Paradex
Report ID: #47257
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/tradeparadex/audit-competition-may-2025/tree/main/paraclear
Impacts:
Temporary freezing of funds for at least 24 hour
Description
Brief/Intro
The protocol does not currently impose a limit on the number of positions an individual account can hold. As the type of futures and options has already exceeded 100 and is expected to grow further in the app, it is necessary to impose a limit on the number of positions per account to prevent potential DoS issues caused by excessive loop iterations.
Vulnerability Details
fn _create_asset_balance(
ref self: ComponentState<TContractState>,
account: ContractAddress,
market: felt252,
amount: felt252,
cost: felt252,
current_funding: felt252,
) -> PerpetualAssetBalance {
// Get current tail of synthetic asset balances
let tail_market = self.Paraclear_perpetual_asset_balance_tail.read(account);
// Create balance for new market
let new_balance = PerpetualAssetBalance {
market: market,
amount: amount,
cost: cost,
cached_funding: current_funding,
prev: tail_market,
next: 0 // UNSET in Cairo 1
};
// Write balance to storage
self.Paraclear_perpetual_asset_balance.write((account, market), new_balance);
// Set new tail
self.Paraclear_perpetual_asset_balance_tail.write(account, market);
if tail_market.is_non_zero() {
// Write updated tail balance to storage
self
.Paraclear_perpetual_asset_balance
.entry((account, tail_market))
.next
.write(market);
}
new_balance
}
A position linked list is maintained for each account, where each type of position held by the account occupies one node in the list.
When executing a trade, we need to load_account
, iterate through the linked list to construct the account_state
, and also loop through to fetch the corresponding market mark prices and asset prices.
There is no restriction on the maximum number of positions a single account can hold. This makes it easy for certain system functions to encounter DoS issues due to excessive iteration.
Impact Details
Certain system functions may suffer from DoS due to excessive iteration over account positions.
The most likely scenario is that a malicious operator, together with sub-operators, opens an excessive number of positions to prevent users from withdrawing assets from the vault, since deposits into the vault do require traversing positions is more less withdrawals.
References
https://github.com/tradeparadex/audit-competition-may-2025/blob/0eb81b26a67666c399b4e16b39a96c19848ab7fd/vaults/src/vault/vault.cairo#L697 https://github.com/tradeparadex/audit-competition-may-2025/blob/0eb81b26a67666c399b4e16b39a96c19848ab7fd/vaults/src/vault/vault.cairo#L1048
Proof of Concept
Proof of Concept
Functions that require iterating over positions about vault function include:
deposit
: calls_convert_to_shares
once and_is_vault_healthy
once.withdraw
: calls_convert_to_shares
once,_convert_to_assets
once, and for each operator and sub-operator, callsaccount_transfer_partial
. Eachaccount_transfer_partial
in turn calls_load_account_v2
,excess_balance
, and_transfer_positions_internal
, each of which iterates over positions.
There are 100 types of positions in the market (futures and option assets).
In Vault 1, a malicious operator intends to prevent users from withdrawing funds.
The malicious operator opens 100 positions—most of which can be minimal—and the sub-operator does the same.
In this case, the number of iterations during a withdraw operation could exceed: 100 * (1 + 1 + 4 * (1 + 1 + 1)) = 1400 iterations.
Was this helpful?