Part 2

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

The vault in the debt state was assessed incorrectly.

Summary

In the settleVaultsDebt function, the vault in the debt state was assessed incorrectly.

Vulnerability Details

function settleVaultsDebt(uint256[] calldata vaultsIds) external onlyRegisteredSystemKeepers {
// first, we need to update the credit capacity of the vaults
Vault.recalculateVaultsCreditCapacity(vaultsIds);
// working data, cache usdc address
SettleVaultDebtContext memory ctx;
ctx.usdc = MarketMakingEngineConfiguration.load().usdc;
// load the usdc collateral data storage pointer
Collateral.Data storage usdcCollateralConfig = Collateral.load(ctx.usdc);
for (uint256 i; i < vaultsIds.length; i++) {
// load the vault storage pointer
Vault.Data storage vault = Vault.loadExisting(vaultsIds[i].toUint128());
// cache the vault's unsettled debt, if zero skip to next vault
// amount in zaros internal precision
ctx.vaultUnsettledRealizedDebtUsdX18 = vault.getUnsettledRealizedDebt();
if (ctx.vaultUnsettledRealizedDebtUsdX18.isZero()) continue;
// otherwise vault has debt to be settled, cache the vault's collateral asset
ctx.vaultAsset = vault.collateral.asset;
// loads the dex swap strategy data storage pointer
DexSwapStrategy.Data storage dexSwapStrategy =
DexSwapStrategy.loadExisting(vault.swapStrategy.assetDexSwapStrategyId);
// if the vault is in debt, swap its assets to USDC
436: 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
466: vault.marketsRealizedDebtUsd -= ctx.usdcOutX18.intoUint256().toInt256().toInt128();
...
} ...
}
function getUnsettledRealizedDebt(Data storage self)
internal
view
returns (SD59x18 unsettledRealizedDebtUsdX18)
{
244: unsettledRealizedDebtUsdX18 =
sd59x18(self.marketsRealizedDebtUsd).add(unary(ud60x18(self.depositedUsdc).intoSD59x18()));
}

As we can see, in the settleVaultsDebt function, if vaultUnsettledRealizedDebtUsdX18 < 0, the vault's assets is swaped to usdc.
At this time, vault.marketsRealizedDebtUsd is decreased by the amount of the swap.
Therefore, vaultUnsettledRealizedDebtUsd is more decreased.
The opposite case works similarly.

Impact

Markets don't work as intended.
When the vault is in a credit state, the keeper's calls will result in the exchange of all the vault's assets.
The usdToken owners could not withdraw their assets, due to insufficient assets in the vault.

Recommendations

ctx.vaultUnsettledRealizedDebtUsdX18 = vault.getUnsettledRealizedDebt();
if (ctx.vaultUnsettledRealizedDebtUsdX18.isZero()) continue;
...
// if the vault is in debt, swap its assets to USDC
-436: if (ctx.vaultUnsettledRealizedDebtUsdX18.lt(SD59x18_ZERO)) {
+436: if (ctx.vaultUnsettledRealizedDebtUsdX18.gt(SD59x18_ZERO)) {
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.