createPosition accepts user funds without checking capital availability or limits
createPosition transfers user collateral and origination fee before checking capital pool availability or per-user/market/token/global caps; if openPosition later reverts, funds remain locked in Created state until cancellation.
Description
When a user calls createPosition(), their USDC (collateral plus origination fee) is transferred into the contract at L503 without any check on whether the protocol can fund the requested leverage. Both the capital pool balance check (capitalPoolBalanceUsdcUnits < protocolCapital at L542) and the four capital limit checks (validateCapitalLimits at L547, per-user, per-market, per-token, and global caps) only occur later when the admin calls openPosition() in a separate transaction that the user has no visibility into.
If any of these checks fail, openPosition reverts on the admin side. The user receives no on-chain feedback, their funds remain locked in Created state. Recovery requires a manual cancelPosition call (currently admin-only per L-01) after cancelDelaySeconds elapses (default 10 minutes).
The capital pool is funded manually via addCapital() by the globalAdmin (2-of-3 cold wallet multisig). There is no automated replenishment, no minimum reserve requirement, and no utilization circuit breaker. During demand surges the pool can drain to zero through normal usage, causing all new position opens to fail simultaneously while multiple users have funds locked.
Recommendation
Add capital availability and limit checks to createPosition before transferring user funds:
// In createPosition(), before the safeTransferFrom:uint256 protocolCapitalUsdcUnits = notionalUsdcUnits - collateralUsdcUnits;if (capitalPoolBalanceUsdcUnits < protocolCapitalUsdcUnits) {revert InsufficientCapital();}PositionLib.validateCapitalLimits(userTotalBorrowedUsdcUnits,marketTotalBorrowedUsdcUnits,tokenTotalBorrowedUsdcUnits,totalProtocolBorrowedUsdcUnits,maxUserCapitalUsdcUnits,maxMarketCapitalUsdcUnits,maxTokenCapitalUsdcUnits,maxTotalCapitalUsdcUnits,msg.sender,marketId,tokenId,protocolCapitalUsdcUnits);
This does not replace the checks in openPosition, both are needed since pool balance and limits can change between calls. The early checks prevent the common case where a user locks funds into a position that is already unfundable at creation time.
Resolution
Acknowledged. Backend pre-flight is the primary gate; recovery available via L-01 user self-cancel.

