Part 2

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

The vault's assets are incorrectly swapped with USDC and vice versa when settling the vualt's debt

Summary

The CreditDelegationbranch.sol's settleVaultsDebt function settles the vault's debt or credit by swapping usdc for assets and vice versa. It incorrectly reads the vault state(whether in credit or debt) and swaps the collaterals

Vulnerability Details

The CreditDelegationbranch.sol's settleVaultsDebt function settles the vault's debt or credit by swapping usdc for assets and vice versa. The ctx.vaultUnsettledRealizedDebtUsdX18 is computed via vault.getUnsettledRealizedDebt function which calculates the unsettledRealizedDebtUsdX18 by adding marketsRealizedDebtUsd and depositedUsdc:

unsettledRealizedDebtUsdX18 =
sd59x18(self.marketsRealizedDebtUsd).add(unary(ud60x18(self.depositedUsdc).intoSD59x18()));

Now in accordance to the intended behaviour of the protocol the vault is considered in debt if the net sum of its Markets debt/credit realizations is positive, meaning that it has received more assets than the UsdTokens it had to issue. And when in debt i.e aforementioned value is positive the vault's assets will be swapped to USDC and it will be the inverse/opposite of it when the vault is in credit, meaning the value is negative then the USDC will be swapped to the assets.

But in the codebase the opposite is going on as it checks vaultUnsettledRealizedDebt > 0

if (ctx.vaultUnsettledRealizedDebtUsdX18.lt(SD59x18_ZERO)) { <-(lt)

that means that this value is negative and the vault is in credit and its USDC needs to be swapped to the vaultAsset but actually in the code the opposite of this is happening, i.e the assets are being swapped with USDC.
The same opposite behaviour is being performed when the vault's value is positive i.e it is in debt.

if (ctx.vaultUnsettledRealizedDebtUsdX18.lt(SD59x18_ZERO)) {
// get swap amount; both input and output in native precision
ctx.swapAmount = calculateSwapAmount(
dexSwapStrategy.dexAdapter,
ctx.usdc,
ctx.vaultAsset,
usdcCollateralConfig.convertSd59x18ToTokenAmount(ctx.vaultUnsettledRealizedDebtUsdX18.abs())
);
// swap the vault's assets to usdc in order to cover the usd denominated debt partially or fully
// both input and output in native precision
ctx.usdcOut = _convertAssetsToUsdc(
vault.swapStrategy.usdcDexSwapStrategyId,
ctx.vaultAsset,
ctx.swapAmount,
vault.swapStrategy.usdcDexSwapPath,
address(this),
ctx.usdc
);
// sanity check to ensure we didn't somehow give away the input tokens
if (ctx.usdcOut == 0) revert Errors.ZeroOutputTokens();
// uint256 -> udc60x18 scaling native precision to zaros internal precision
ctx.usdcOutX18 = usdcCollateralConfig.convertTokenAmountToUd60x18(ctx.usdcOut);
// use the amount of usdc bought with assets to update the vault's state
// note: storage updates must be done using zaros internal precision
//
// deduct the amount of usdc swapped for assets from the vault's unsettled debt
vault.marketsRealizedDebtUsd -= ctx.usdcOutX18.intoUint256().toInt256().toInt128();
// allocate the usdc acquired to back the engine's usd token
UsdTokenSwapConfig.load().usdcAvailableForEngine[vault.engine] += ctx.usdcOutX18.intoUint256();
// update the variables to be logged
ctx.assetIn = ctx.vaultAsset;
ctx.assetInAmount = ctx.swapAmount;
ctx.assetOut = ctx.usdc;
ctx.assetOutAmount = ctx.usdcOut;
// since we're handling debt, we provide a positive value
ctx.settledDebt = ctx.usdcOut.toInt256();
} else {
// else vault is in credit, swap its USDC previously accumulated
// from market and vault deposits into its underlying asset
// get swap amount; both input and output in native precision
ctx.usdcIn = calculateSwapAmount(
dexSwapStrategy.dexAdapter,
ctx.vaultAsset,
ctx.usdc,
usdcCollateralConfig.convertSd59x18ToTokenAmount(ctx.vaultUnsettledRealizedDebtUsdX18.abs())
);
// get deposited USDC balance of the vault, convert to native precision
ctx.vaultUsdcBalance = usdcCollateralConfig.convertUd60x18ToTokenAmount(ud60x18(vault.depositedUsdc));
// if the vault doesn't have enough usdc use whatever amount it has
// make sure we compare native precision values together and output native precision
ctx.usdcIn = (ctx.usdcIn <= ctx.vaultUsdcBalance) ? ctx.usdcIn : ctx.vaultUsdcBalance;
// swaps the vault's usdc balance to more vault assets and
// send them to the ZLP Vault contract (index token address)
// both input and output in native precision
ctx.assetOutAmount = _convertUsdcToAssets(
vault.swapStrategy.assetDexSwapStrategyId,
ctx.vaultAsset,
ctx.usdcIn,
vault.swapStrategy.assetDexSwapPath,
vault.indexToken,
ctx.usdc
);
// sanity check to ensure we didn't somehow give away the input tokens
if (ctx.assetOutAmount == 0) revert Errors.ZeroOutputTokens();
// subtract the usdc amount used to buy vault assets from the vault's deposited usdc, thus, settling
// the due credit amount (partially or fully).
// note: storage updates must be done using zaros internal precision
vault.depositedUsdc -= usdcCollateralConfig.convertTokenAmountToUd60x18(ctx.usdcIn).intoUint128();
// update the variables to be logged
ctx.assetIn = ctx.usdc;
ctx.assetInAmount = ctx.usdcIn;
ctx.assetOut = ctx.vaultAsset;
// since we're handling credit, we provide a negative value
ctx.settledDebt = -ctx.usdcIn.toInt256();
}

Tools Used

manual Review

Recommendations

refactor the code so it works as intended, meaning that when the vault is in debt(positive value, greater than 0) then swap its assets to USDC and if its in credit, then do the inverse, swap usdc to asset.

Updates

Lead Judging Commences

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

settleVaultDebt functions in opposite direction because of `ctx.vaultUnsettledRealizedDebtUsdX18.lt(SD59x18_ZERO)` having an inversed comparator (should have been gt)

Support

FAQs

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