Part 2

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

`getVaultAccumulatedValues` will always return wrong value

Summary

In the getVaultAccumulatedValues the it is meant to get the accumulated values for a vault from a market. However the formula used is incorrect.

Vulnerability Details

When recalculateVaultsCreditCapacity is called, for each vault it will perform _recalculateConnectedMarketsState. Since the one market can be connected to two vaults the following operations are made in order to calculate the values only for the specific vault:

  1. For each market it will load the debt:

ctx.marketUnrealizedDebtUsdX18 = market.getUnrealizedDebtUsd();
ctx.marketRealizedDebtUsdX18 = market.getRealizedDebtUsd();
  1. After that it will distribute the debt to the vaults:

if (!ctx.marketUnrealizedDebtUsdX18.isZero() || !ctx.marketRealizedDebtUsdX18.isZero()) {
// distribute the market's debt to its connected vaults
market.distributeDebtToVaults(ctx.marketUnrealizedDebtUsdX18, ctx.marketRealizedDebtUsdX18);
}
  1. To distribute the debt to all the vaults it will calculate the debt per share, which is debt/(credit from all the vaults for the market):

function distributeDebtToVaults(
Data storage self,
SD59x18 newUnrealizedDebtUsdX18,
SD59x18 newRealizedDebtUsdX18
)
internal
{
SD59x18 totalVaultSharesX18 = ud60x18(self.totalDelegatedCreditUsd).intoSD59x18();
if (totalVaultSharesX18.isZero()) {
revert Errors.NoDelegatedCredit(self.id);
}
self.realizedDebtUsdPerVaultShare = newRealizedDebtUsdX18.div(totalVaultSharesX18).intoInt256().toInt128();
self.unrealizedDebtUsdPerVaultShare = newUnrealizedDebtUsdX18.div(totalVaultSharesX18).intoInt256().toInt128();
}
  1. After the debt per delegated credit is calculated getVaultAccumulatedValues is called in order to calculate the debt for the specific vault.

  2. However the getVaultAccumulatedValues will use wrong formula to calculate the accumulated values:

// calculate the vault's share of the total delegated credit, from 0 to 1
UD60x18 vaultCreditShareX18 = vaultDelegatedCreditUsdX18.div(getTotalDelegatedCreditUsd(self));//it is the share of the vault for a given market
// calculate the vault's value changes since its last accumulation
// note: if the last distributed value is zero, we assume it's the first time the vault is accumulating
// values, thus, it needs to return zero changes
realizedDebtChangeUsdX18 = !lastVaultDistributedRealizedDebtUsdPerShareX18.isZero()
? sd59x18(self.realizedDebtUsdPerVaultShare).sub(lastVaultDistributedRealizedDebtUsdPerShareX18).mul(
vaultCreditShareX18.intoSD59x18()
)
: SD59x18_ZERO;

Here we can see that the vaultCreditShareX18 is calculated. However in the realizedDebtChangeUsdX18 as the values are per share, the vaultDelegatedCreditUsdX18 should be used in order to calculate the realizedDebtChangeUsdX18 instead of vaultCreditShareX18.
Consider the following scenario:
1.There VaultA and VaultB which are connected to MarketAB. Both vaults have delegated 50USD as credit meaning that the total delegated for the market will be 100USD. When the market accrues a realized debt of 10 USD, using the above formula the debt for vault A will be calculated the following way:
realizedDebtUsdPerVaultShare = 10/100 = 0.1e18USD
vaultCreditShareX18 = 50/100 = 0.5e18
realizedDebtChangeUsdX18 = 0.5*0.1 = 0.05USD instead of the 5 USD it should receive as debt. (in the example we assume that lastVaultDistributedRealizedDebtUsdPerShareX18 is not zero but a very small negligible value)

Impact

_recalculateConnectedMarketsState will always return wrong values.

Tools Used

Manual Review

Recommendations

Multiply by vaultDelegatedCreditUsdX18 instead of totalVaultSharesX18.

Updates

Lead Judging Commences

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

`market.getVaultAccumulatedValues` returns a lower value

Support

FAQs

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