Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

Improper Market ID Caching Leads to Faulty Credit Capacity Calculations

Summary

A critical vulnerability exists in the Vault contract's credit capacity recalculation logic where connected market IDs are improperly cached. The code initializes an empty array for market IDs but fails to populate it with actual data from storage. This results in:

  1. Invalid Market References - Subsequent calculations use zero-value market IDs that don't exist

  2. Broken Risk Management - Credit delegation and exposure limits become mathematically meaningless

  3. Protocol-Wide Accounting Errors - All vault credit capacity calculations reference non-existent markets

This flaw fundamentally undermines the protocol's ability to maintain accurate collateralization ratios, creating systemic risk of undercollateralized positions and potential protocol insolvency during normal operations.

Vulnerability Details

The vulnerability occurs in the credit capacity recalculation logic where connected market IDs are improperly cached. In function Vault.recalculateVaultsCreditCapacity (Vault.sol#L386), the code initializes an empty array for market IDs cache but fails to populate it with actual data from storage. This results in subsequent functions:

  1. Vault.updateVaultAndCreditDelegationWeight

  2. Vault._recalculateConnectedMarketsState

  3. Vault._updateCreditDelegations

operating on invalid market ID data. The cache array length is correctly set but remains unpopulated, causing all market ID values to default to zero (empty array values in Solidity memory):

// Vault.recalculateVaultsCreditCapacity()
// loads the connected markets storage pointer by taking the last configured market ids uint set
EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1];
// cache the connected markets ids to avoid multiple storage reads, as we're going to loop over them twice
// at `_recalculateConnectedMarketsState` and `_updateCreditDelegations`
@> uint128[] memory connectedMarketsIdsCache = new uint128[](connectedMarkets.length());
// update vault and credit delegation weight
updateVaultAndCreditDelegationWeight(self, connectedMarketsIdsCache);
// iterate over each connected market id and distribute its debt so we can have the latest credit
// delegation of the vault id being iterated to the provided `marketId`
(
uint128[] memory updatedConnectedMarketsIdsCache,
SD59x18 vaultTotalRealizedDebtChangeUsdX18,
SD59x18 vaultTotalUnrealizedDebtChangeUsdX18,
UD60x18 vaultTotalUsdcCreditChangeX18,
UD60x18 vaultTotalWethRewardChangeX18
@> ) = _recalculateConnectedMarketsState(self, connectedMarketsIdsCache, true);
// ...
// update the vault's credit delegations
(, SD59x18 vaultNewCreditCapacityUsdX18) =
_updateCreditDelegations(self, updatedConnectedMarketsIdsCache, false);
// Vault._recalculateConnectedMarketsState()
@> rehydratedConnectedMarketsIdsCache = new uint128[](connectedMarketsIdsCache.length);
// cache the vault id
ctx.vaultId = self.id;
// cache the connected markets length
uint256 connectedMarketsConfigLength = self.connectedMarkets.length;
// loads the connected markets storage pointer by taking the last configured market ids uint set
EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1];
for (uint256 i; i < connectedMarketsIdsCache.length; i++) {
if (shouldRehydrateCache) {
rehydratedConnectedMarketsIdsCache[i] = connectedMarkets.at(i).toUint128();
} else {
@> rehydratedConnectedMarketsIdsCache[i] = connectedMarketsIdsCache[i];
}
// loads the market storage pointer
Market.Data storage market = Market.load(rehydratedConnectedMarketsIdsCache[i]);
// ...
// load the credit delegation to the given market id
CreditDelegation.Data storage creditDelegation =
CreditDelegation.load(ctx.vaultId, rehydratedConnectedMarketsIdsCache[i]);
// ...
}
}

This flaw fundamentally breaks the credit capacity calculation process as:

  • Zero market IDs don't correspond to actual markets

  • Risk exposure calculations become mathematically invalid

  • Protocol state transitions occur based on non-existent markets

The root cause is the missing population loop between cache initialization and usage. The cache array should be filled with actual market IDs from the connectedMarkets EnumerableSet storage before being used in downstream calculations.

Impact

This critical accounting error directly impacts protocol solvency and risk management in three key ways:

  1. Incorrect Credit Capacity Exposure
    Vaults will report inflated/incorrect credit availability to markets, enabling over-leveraged positions that could lead to protocol insolvency during market volatility

  2. Broken Risk Isolation
    The core safety mechanism of market-specific credit delegation fails, allowing risk contagion between unrelated markets through miscalculated exposure limits

  3. LP Fund Mismanagement
    Liquidity provider deposits become misallocated as credit delegations reference non-existent markets, potentially leading to unrecoverable fund lockups

The vulnerability fundamentally undermines the protocol's ability to maintain proper collateralization ratios, creating systemic risk where properly collateralized positions could be liquidated due to faulty global credit calculations. This could lead to direct fund losses for LPs and traders through improper liquidations and credit allocation.

Tools Used

Manual Review

Recommendations

The critical fix requires populating the market ID cache with actual data from storage. Modify the affected code section in Vault.sol#L386 to:

// Initialize array with correct length
uint128[] memory connectedMarketsIdsCache = new uint128[](connectedMarkets.length());
// Populate with actual market IDs from storage
for (uint256 i = 0; i < connectedMarkets.length(); i++) {
connectedMarketsIdsCache[i] = uint128(connectedMarkets.at(i));
}

This change ensures the cache contains real market IDs before being used in credit capacity calculations. The loop converts the EnumerableSet storage into a memory array of valid market IDs, preserving data integrity for downstream operations.

Updates

Lead Judging Commences

inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.