Part 2

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

`Vault.recalculateVaultsCreditCapacity` regards the shares of all connected markets as 100%.

Summary

Vault.recalculateVaultsCreditCapacity updates the vault data like marketsRealizedDebtUsd before major user actions including VaultRouterBranch.deposit and VaultRouterBranch.redeem.
However, Vault.updateVaultAndCreditDelegationWeight saves all credit delegations as equal to the vault's total credit delegation weight self.totalCreditDelegationWeight.
This results in wrong marketsRealizedDebtUsd, marketsUnrealizedDebtUsd and depositedUsdc in Vault.Data.

for (uint256 i; i < connectedMarketsIdsCache.length; i++) {
// load the credit delegation to the given market id
CreditDelegation.Data storage creditDelegation =
CreditDelegation.load(self.id, connectedMarkets.at(i).toUint128());
// update the credit delegation weight
creditDelegation.weight = newWeight;
}
// update the vault weight
self.totalCreditDelegationWeight = newWeight;

Vulnerability Details

creditDelegationShareX18 is always 1e18. Therefore, vaultCreditCapacityUsdX18 is not distributed proportionally to the credit delegations.

UD60x18 creditDelegationShareX18 =
ud60x18(creditDelegation.weight).div(ud60x18(totalCreditDelegationWeightCache));
// stores the vault's total credit capacity to be returned
vaultCreditCapacityUsdX18 = getTotalCreditCapacityUsd(self);
// if the vault's credit capacity went to zero or below, we set its credit delegation to that market
// to zero
UD60x18 newCreditDelegationUsdX18 = vaultCreditCapacityUsdX18.gt(SD59x18_ZERO)
? vaultCreditCapacityUsdX18.intoUD60x18().mul(creditDelegationShareX18)
: UD60x18_ZERO;

This updates valudUsd bigger than the actual value.

creditDelegation.valueUsd = newCreditDelegationUsdX18.intoUint128();

It results in wrong ctx.realizedDebtChangeUsdX18, ctx.unrealizedDebtChangeUsdX18 and ctx.usdcCreditChangeX18 from market.getVaultAccumulatedValues.

(
ctx.realizedDebtChangeUsdX18,
ctx.unrealizedDebtChangeUsdX18,
ctx.usdcCreditChangeX18,
ctx.wethRewardChangeX18
) = market.getVaultAccumulatedValues(
ud60x18(creditDelegation.valueUsd),
sd59x18(creditDelegation.lastVaultDistributedRealizedDebtUsdPerShare),
sd59x18(creditDelegation.lastVaultDistributedUnrealizedDebtUsdPerShare),
ud60x18(creditDelegation.lastVaultDistributedUsdcCreditPerShare),
ud60x18(creditDelegation.lastVaultDistributedWethRewardPerShare)
);

Impact

These 3 return values are wrong in Vault._recalculateConnectedMarketsState.

// update the vault's state by adding its share of the market's latest state variables
vaultTotalRealizedDebtChangeUsdX18 = vaultTotalRealizedDebtChangeUsdX18.add(ctx.realizedDebtChangeUsdX18);
vaultTotalUnrealizedDebtChangeUsdX18 =
vaultTotalUnrealizedDebtChangeUsdX18.add(ctx.unrealizedDebtChangeUsdX18);
vaultTotalUsdcCreditChangeX18 = vaultTotalUsdcCreditChangeX18.add(ctx.usdcCreditChangeX18);

As a result, Vault.recalculateVaultsCreditCapacity stores wrong marketsRealizedDebtUsd, marketsUnrealizedDebtUsd and depositedUsdc in Vault.Data.

(
uint128[] memory updatedConnectedMarketsIdsCache,
SD59x18 vaultTotalRealizedDebtChangeUsdX18,
SD59x18 vaultTotalUnrealizedDebtChangeUsdX18,
UD60x18 vaultTotalUsdcCreditChangeX18,
UD60x18 vaultTotalWethRewardChangeX18
) = _recalculateConnectedMarketsState(self, connectedMarketsIdsCache, true);
// gas optimization: only write to storage if values have changed
//
// updates the vault's stored unsettled realized debt distributed from markets
if (!vaultTotalRealizedDebtChangeUsdX18.isZero()) {
self.marketsRealizedDebtUsd = sd59x18(self.marketsRealizedDebtUsd).add(
vaultTotalRealizedDebtChangeUsdX18
).intoInt256().toInt128();
}
// updates the vault's stored unrealized debt distributed from markets
if (!vaultTotalUnrealizedDebtChangeUsdX18.isZero()) {
self.marketsUnrealizedDebtUsd = sd59x18(self.marketsUnrealizedDebtUsd).add(
vaultTotalUnrealizedDebtChangeUsdX18
).intoInt256().toInt128();
}
// adds the vault's total USDC credit change, earned from its connected markets, to the
// `depositedUsdc` variable
if (!vaultTotalUsdcCreditChangeX18.isZero()) {
self.depositedUsdc = ud60x18(self.depositedUsdc).add(vaultTotalUsdcCreditChangeX18).intoUint128();
}

Tools Used

Manual review

Recommendations

newWeight is uint128(IERC4626(self.indexToken).totalAssets());, so distribute proportionally to the credit delegations.

- creditDelegation.weight = newWeight;
+ creditDelegation.weight = newWeight * creditDelegation.weight / self.totalCreditDelegationWeight;
Updates

Lead Judging Commences

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

Market Credit Delegation Weights Are Incorrectly Distributed

Support

FAQs

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