USDC sent directly to the vault is permanently irrecoverable
rescueERC20 explicitly blocks USDC recovery to protect operational balances, but no alternative mechanism exists, so USDC sent outside the tracked accounting paths is permanently stuck.
Description
The rescueERC20 function explicitly blocks USDC recovery with CannotRescueOperationalToken at L1620. This is intentional, the contract holds operational USDC and a naive rescue function could drain protocol balances. However, no alternative rescue mechanism exists, meaning any USDC that enters the contract outside the tracked accounting paths is permanently stuck.
This can happen through a user accidentally sending USDC to the contract address instead of calling createPosition, or a frontend/integration bug routing funds to the wrong address.
The contract tracks USDC across five buckets: capitalPoolBalanceUsdcUnits, accumulatedFeesUsdcUnits, pendingOriginationFeesUsdcUnits, per-position collateralBalanceUsdcUnits (Created state only), and per-position pendingRefundUsdcUnits. Any USDC outside these buckets is inaccessible, but two of these buckets (collateralBalanceUsdcUnits and pendingRefundUsdcUnits) exist only at the position level with no global accumulator, so the contract cannot compute its own tracked total on-chain to distinguish stuck funds from operational funds.
Recommendation
Add two global accumulator state variables to track the per-position USDC buckets:
uint256 public totalCollateralBalanceUsdcUnits;uint256 public totalPendingRefundUsdcUnits;
Maintain them at all mutation points:
totalCollateralBalanceUsdcUnits(3 sites): increment atcreatePosition, decrement atopenPosition(L575), decrement atcancelPosition.totalPendingRefundUsdcUnits(3 sites): increment atfinalizeOpen(partial fill), increment atfinalizeForceUnwind(non-terminal path), decrement atCloseLib.finalizePositionClose(whenpendingRefundUsdcUnitsis zeroed).
Then add a timelock-gated rescue that can only withdraw the provably untracked excess:
function rescueUSDC(uint256 amountUsdcUnits) external nonReentrant onlyTimelock {uint256 trackedTotal = capitalPoolBalanceUsdcUnits+ accumulatedFeesUsdcUnits+ pendingOriginationFeesUsdcUnits+ totalCollateralBalanceUsdcUnits+ totalPendingRefundUsdcUnits;uint256 actualBalance = IERC20(usdcToken).balanceOf(address(this));uint256 excess = actualBalance - trackedTotal; // reverts on underflowif (amountUsdcUnits > excess) revert InsufficientExcess();IERC20(usdcToken).safeTransfer(globalAdmin, amountUsdcUnits);}
This preserves the original safety concern (admin cannot touch operational USDC) while making accidentally transferred USDC recoverable.
Resolution
Acknowledged. USDC rescue not added on contract-size grounds; existing rescue paths hardened to onlyTimelock.

