Part 2

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

Incorrect Debt Check in `CreditDelegationBranch::settleVaultsDebt` Function

Summary:

The CreditDelegationBranch::settleVaultsDebt function incorrectly determines whether a vault is in debt or credit due to an inverted conditional check. This issue causes incorrect asset swaps, leading to unintended fund movements and potential financial losses within the protocol.


Issue Description:

The settleVaultsDebt function evaluates whether a vault has unsettled debt using the following condition:

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/35deb3e92b2a32cd304bf61d27e6071ef36e446d/src/market-making/branches/CreditDelegationBranch.sol#L436

if (ctx.vaultUnsettledRealizedDebtUsdX18.lt(SD59x18_ZERO)) {
// Vault is considered to be in debt
} else {
// Vault is considered to be in credit
}

Here, ctx.vaultUnsettledRealizedDebtUsdX18 represents the vault's unsettled realized debt in the system's internal precision format. The issue arises from the incorrect interpretation of the sign of vaultUnsettledRealizedDebtUsdX18.

The getUnsettledRealizedDebt function calculates the unsettled debt as follows:

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/35deb3e92b2a32cd304bf61d27e6071ef36e446d/src/market-making/leaves/Vault.sol#L239

function getUnsettledRealizedDebt(Data storage self)
internal
view
returns (SD59x18 unsettledRealizedDebtUsdX18)
{
unsettledRealizedDebtUsdX18 =
sd59x18(self.marketsRealizedDebtUsd).add(unary(ud60x18(self.depositedUsdc).intoSD59x18()));
}

This formula can be broken down as:

UnsettledRealizedDebt = marketsRealizedDebtUsd + (-depositedUsdc)

Examples:

  1. Vault in Debt:

    • marketsRealizedDebtUsd = 100,000

    • depositedUsdc = 80,000

    • UnsettledRealizedDebt = 100,000 + (-80,000) = 20,000

    • The vault has 20,000 debt to settle, so the protocol should swap assets to USDC to cover this debt.

  2. Vault in Credit:

    • marketsRealizedDebtUsd = 100,000

    • depositedUsdc = 120,000

    • UnsettledRealizedDebt = 100,000 + (-120,000) = -20,000

    • The vault has a 20,000 credit, so the protocol should swap USDC to assets.


Impact

  1. Incorrect Asset Swaps:

    • A vault with positive vaultUnsettledRealizedDebtUsdX18 (actual debt) is incorrectly treated as having a surplus (credit), preventing proper debt settlement.

    • A vault with negative vaultUnsettledRealizedDebtUsdX18 (actual credit) is incorrectly treated as having debt, causing unnecessary swaps from USDC to assets.

  2. Potential Financial Loss:

    • Failure to settle real debt create inconsistencies in the protocol’s accounting system, potentially affecting liquidity and solvency.


Root Cause

The root cause is the incorrect interpretation of the sign of vaultUnsettledRealizedDebtUsdX18:

  • Positive Value: Indicates debt (realized debt exceeds USDC deposits).

  • Negative Value: Indicates credit (USDC deposits exceed realized debt).

The current implementation incorrectly treats a negative value as debt and a positive value as credit.


Recommended Fix:

The condition should be updated to correctly distinguish between debt and credit:

if (ctx.vaultUnsettledRealizedDebtUsdX18.gt(SD59x18_ZERO)) {
// Vault is in debt: swap assets to USDC
} else {
// Vault is in credit: swap USDC to assets
}

This change ensures that:

  • If vaultUnsettledRealizedDebtUsdX18 > 0, the vault is in debt and should swap assets to USDC.

  • If vaultUnsettledRealizedDebtUsdX18 < 0, the vault is in credit and should swap USDC to assets.

**Note on marketRealizedDebt Sign

The marketRealizedDebt value should always be positive when a vault is in debt. This is because the formula for calculating UnsettledRealizedDebt is:

UnsettledRealizedDebt = marketsRealizedDebtUsd + (-depositedUsdc)

If marketsRealizedDebtUsd were negative, the calculation would result in:

UnsettledRealizedDebt = -marketsRealizedDebtUsd + (-depositedUsdc)

Given that depositedUsdc is always positive (as it represents the amount of USDC deposited into the vault), this would lead to:

UnsettledRealizedDebt = -marketsRealizedDebtUsd - depositedUsdc

This would always result in a negative value, which does not make sense in the context of debt and credit calculations. For example:

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

settleVaultDebt functions in opposite direction because of `ctx.vaultUnsettledRealizedDebtUsdX18.lt(SD59x18_ZERO)` having an inversed comparator (should have been gt)

Support

FAQs

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