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;