Description
VaultRouterBranch::getVaultCreditCapacity
, VaultRouterBranch::getIndexTokenSwapRate
, Vault::getTotalCreditCapacityUsd
, Vault::updateVaultAndCreditDelegationWeight
and ZlpVault::maxDeposit
all use the standard EIP - 4626 totalAssets()
for crucial accounting functions.
Vulnerable Code
VaultRouterBranch::getVaultCreditCapacity
:
function getVaultCreditCapacity(uint128 vaultId) public view returns (uint256) {
Vault.Data storage vault = Vault.loadExisting(vaultId);
1. @> SD59x18 totalAssetsX18 = @>vault.collateral.convertTokenAmountToSd59x18(IERC4626(vault.indexToken).totalAssets().toInt256());
SD59x18 vaultDebtUsdX18 = vault.getTotalDebt();
UD60x18 assetPriceX18 = vault.collateral.getPrice();
SD59x18 vaultDebtInAssetsX18 = vaultDebtUsdX18.div(assetPriceX18.intoSD59x18());
uint8 decimalOffset = Constants.SYSTEM_DECIMALS - vault.collateral.decimals;
SD59x18 totalAssetsMinusVaultDebtX18 =
totalAssetsX18.add(sd59x18(int256(10 ** uint256(decimalOffset)))).sub(vaultDebtInAssetsX18);
uint256 totalAssetsMinusVaultDebt = vault.collateral.convertSd59x18ToTokenAmount(totalAssetsMinusVaultDebtX18);
return totalAssetsMinusVaultDebt;
}
VaultRouterBranch::getIndexTokenSwapRate
function getIndexTokenSwapRate(
uint128 vaultId,
uint256 sharesIn,
bool shouldDiscountRedeemFee
)
public
view
returns (UD60x18 assetsOut)
{
Vault.Data storage vault = Vault.loadExisting(vaultId);
2. @> uint256 totalAssetsMinusVaultDebt = getVaultCreditCapacity(vaultId);
uint8 decimalOffset = Constants.SYSTEM_DECIMALS - IERC20Metadata(vault.indexToken).decimals();
uint256 previewAssetsOut = sharesIn.mulDiv(
totalAssetsMinusVaultDebt,
IERC4626(vault.indexToken).totalSupply() + 10 ** decimalOffset,
MathOpenZeppelin.Rounding.Floor
);
if (shouldDiscountRedeemFee) {
previewAssetsOut = ud60x18(previewAssetsOut).sub(ud60x18(previewAssetsOut).mul(ud60x18(vault.redeemFee))).intoUint256();
return ud60x18(previewAssetsOut);
}
Vault::getTotalCreditCapacityUsd
:
function getTotalCreditCapacityUsd(Data storage self) internal view returns (SD59x18 creditCapacityUsdX18) {
Collateral.Data storage collateral = self.collateral;
3. @> UD60x18 totalAssetsX18 = ud60x18(IERC4626(self.indexToken).totalAssets());
UD60x18 totalAssetsUsdX18 = collateral.getAdjustedPrice().mul(totalAssetsX18);
creditCapacityUsdX18 = totalAssetsUsdX18.intoSD59x18().sub(getTotalDebt(self));
}
Vault::updateVaultAndCreditDelegationWeight
:
function updateVaultAndCreditDelegationWeight(
Data storage self,
uint128[] memory connectedMarketsIdsCache
)
internal
{
uint256 connectedMarketsConfigLength = self.connectedMarkets.length;
EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1];
4. @> uint128 newWeight = uint128(IERC4626(self.indexToken).totalAssets());
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;
}
ZlpVault::maxDeposit
function maxDeposit(address) public view override returns (uint256 maxAssets) {
ZlpVaultStorage storage zlpVaultStorage = _getZlpVaultStorage();
IMarketMakingEngine marketMakingEngine = IMarketMakingEngine(zlpVaultStorage.marketMakingEngine);
uint128 depositCap = marketMakingEngine.getDepositCap(zlpVaultStorage.vaultId);
5. @> uint256 totalAssetsCached = totalAssets();
unchecked {
maxAssets = depositCap > totalAssetsCached ? depositCap - totalAssetsCached : 0;
}
}
As you can see in the provided code snippets through point 1. - 5. there are several functions relying on totalAssets()
which essentially is balanceOf()
accounting, and therefore easily manipulatable via token donations to the Vaults.
Impact
The impact in within the function varies, but is overall within high severity. Crucial steps within the redemption of user claims like in 1. and 2. as you can see the previewAssetsOut
is affected.
In 3. the debt calculations of the Vault will cause to report a wrong creditCapacity
which will make the Vault believe it actually has more credit capacity than it actually has, therefore affecting the overall health and critical invariants of the system (solvency invariant).
Since the system has no way of dealing with such donations (withdraw functions are limited to 0, also there is no way on integrating it as a donation into the vault), I rate the severity as High.
Tools Used
Manual Review
Recommended Mitigation
Instead of relying on totalAssets()
which is a manipulatable value, consider switching into an internal accounting scheme.