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++) {
CreditDelegation.Data storage creditDelegation =
CreditDelegation.load(self.id, connectedMarkets.at(i).toUint128());
creditDelegation.weight = newWeight;
}
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));
vaultCreditCapacityUsdX18 = getTotalCreditCapacityUsd(self);
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
.
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);
if (!vaultTotalRealizedDebtChangeUsdX18.isZero()) {
self.marketsRealizedDebtUsd = sd59x18(self.marketsRealizedDebtUsd).add(
vaultTotalRealizedDebtChangeUsdX18
).intoInt256().toInt128();
}
if (!vaultTotalUnrealizedDebtChangeUsdX18.isZero()) {
self.marketsUnrealizedDebtUsd = sd59x18(self.marketsUnrealizedDebtUsd).add(
vaultTotalUnrealizedDebtChangeUsdX18
).intoInt256().toInt128();
}
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;