Part 2

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

Collateral price mismatch in multi-vault swaps

Summary

The initiateSwap function incorrectly assumes that all vaults in the vaultIds array share the same collateral price. It initializes ctx.collateralPriceX18 using the first vault's price and reuses it for all swaps in the batch, potentially leading to incorrect asset calculations.

Vulnerability Details

Vault.Data storage currentVault = Vault.load(vaultIds[0]);
ctx.collateralPriceX18 = currentVault.collateral.getPrice();

The function sets ctx.collateralPriceX18 once before the loop using the first vault’s price (vaultIds[0]), this can lead to underpriced or overpriced swaps, depending on how the price of the first vault differs from the others.

Impact

A malicious user could manipulate the vault order in vaultIds to use a lower price vault first, allowing them to receive more collateral than expected when swapping from a higher-price vault.

Recommended Mitigation

Update ctx.collateralPriceX18 inside the loop to ensure each vault uses its correct collateral price:

[src/market-making/branches/StabilityBranch.sol]
. . .
238 for (uint256 i; i < amountsIn.length; i++) {
239 // for all but first iteration, refresh the vault and enforce same collateral asset
240 if (i != 0) {
241 currentVault = Vault.load(vaultIds[i]);
242
243 // revert for swaps using vaults with different collateral assets
244 if (currentVault.collateral.asset != ctx.initialVaultCollateralAsset) {
245 revert Errors.VaultsCollateralAssetsMismatch();
246 }
247
248 // refresh current vault balance in native precision of ctx.initialVaultCollateralAsset
+ ctx.collateralPriceX18 = currentVault.collateral.getPrice();
249 ctx.vaultAssetBalance = IERC20(ctx.initialVaultCollateralAsset).balanceOf(currentVault.indexToken);
250 }
251
252 // cache the expected amount of assets acquired with the provided parameters
253 // amountsIn[i] and ctx.collateralPriceX18 using zaros internal precision
254 ctx.expectedAssetOut =
255 getAmountOfAssetOut(vaultIds[i], ud60x18(amountsIn[i]), ctx.collateralPriceX18).intoUint256();
256
257 // revert if the slippage wouldn't pass or the expected output was 0
258 if (ctx.expectedAssetOut == 0) revert Errors.ZeroOutputTokens();
259 if (ctx.expectedAssetOut < minAmountsOut[i]) {
260 revert Errors.SlippageCheckFailed(minAmountsOut[i], ctx.expectedAssetOut);
261 }
. . .
Updates

Lead Judging Commences

inallhonesty Lead Judge
7 months ago
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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