Part 2

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

Decimal Precision Mismatch Causing Incorrect Swap Reverts in `StabilityBranch.initiateSwap()`

Summary

Due to a mismatch in decimal handling within StabilityBranch.initiateSwap(), vaults with low-decimal underlying assets (e.g. USDC or wBTC) may incorrectly revert the check for sufficient assets. This can cause the function to reject swap requests even when the vault has enough funds, preventing valid calls to StabilityBranch.initiateSwap().

Vulnerability Details

When StabilityBranch.initiateSwap() is called, it performs various checks, including verifying if the target vault has enough underlying assets to fulfill the swap request (See code snippet from StabilityBranch.initiateSwap() below).

// if there aren't enough assets in the vault to fulfill the swap request, we must revert
if (ctx.vaultAssetBalance < ctx.expectedAssetOut) {
revert Errors.InsufficientVaultBalance(vaultIds[i], ctx.vaultAssetBalance, ctx.expectedAssetOut);
}

However, there is a problem because ctx.vaultAssetBalance has the same number of decimals as that vault's underlying asset, but ctx.expectedAssetOut has Zaros internal precision (18 decimals). As a consequence, for vaults with underlying assets that have low decimals (like USDC or wBTC), the (ctx.vaultAssetBalance < ctx.expectedAssetOut) check will revert even when there are enough assets for the swap to be fulfilled correctly.

This issue is not caught by the test suite because it provides unrealistic amounts of assets to the vault, so even with the decimal mismatch, the (ctx.vaultAssetBalance < ctx.expectedAssetOut) check passes. For example, one of the potentially affected vault's underlying assets is USDC (6 decimals). In the tests in initiateSwap.t.sol (test/integration/market-making/stability-branch/initiateSwap/initiateSwap.t.sol), depositCap, which equals 2e18, is deposited to the vault during testing. For USDC, this corresponds to approximately 2,000,000,000,000 USD (2 trillion dollars). Due to these unrealistically high amounts of assets given to the vaults, the check (ctx.vaultAssetBalance < ctx.expectedAssetOut) passes during tests without issue.

However, during more realistic scenarios, the check will fail regardless of whether there are enough assets to fulfill the swap request or not on those vaults with low decimals assets. Check the following POC for an example scenario (apply the diff patch below to test/integration/market-making/stability-branch/initiateSwap/initiateSwap.t.sol and run forge build && forge test --mt POC)

diff --git a/test/integration/market-making/stability-branch/initiateSwap/initiateSwap.t.sol b/test/integration/market-making/stability-branch/ini
tiateSwap/initiateSwap.t.sol
index b847839..89614fa 100644
--- a/test/integration/market-making/stability-branch/initiateSwap/initiateSwap.t.sol
+++ b/test/integration/market-making/stability-branch/initiateSwap/initiateSwap.t.sol
@@ -9,6 +9,8 @@ import { UsdTokenSwapConfig } from "@zaros/market-making/leaves/UsdTokenSwapConf
import { Collateral } from "@zaros/market-making/leaves/Collateral.sol";
import { IPriceAdapter } from "@zaros/utils/PriceAdapter.sol";
import { IERC4626 } from "@openzeppelin/interfaces/IERC4626.sol";
+import "@openzeppelin/interfaces/IERC20Metadata.sol";
+import "@zaros/utils/Math.sol";
// PRB Math dependencies
import { ud60x18, UD60x18 } from "@prb-math/UD60x18.sol";
@@ -155,6 +157,59 @@ contract InitiateSwap_Integration_Test is Base_Test {
assertFalse(request.processed);
}--
+ function testPOC () external
+ whenVaultIdsAndAmountsInArraysLengthMatch
+ whenAmountsInAndMinAmountsOutArraysLengthMatch
+ whenCollateralIsEnabled
+ {--
+ // START OF SETUP //
+ // Select vaultId = 8 because its underlying asset is USDC
+ VaultConfig memory fuzzVaultConfig = getFuzzVaultConfig(8);
+
+ // Ensure that vault asset is 6 decimals
+ assertEq(IERC20Metadata(fuzzVaultConfig.asset).decimals(), 6);
+
+ // Give vault $10M
+ deal({
+ token: address(fuzzVaultConfig.asset),
+ to: fuzzVaultConfig.indexToken,
+ give: 10_000_000 * 1e6
+ });
+
+ uint256 swapAmount = 1000e6;
+ // Give user $1000
+ deal({ token: address(usdToken), to: users.naruto.account, give: swapAmount });
+
+ uint256 vaultAssetBalance = IERC4626(fuzzVaultConfig.indexToken).totalAssets();
+
+ // amount to be swapped must be in Zaros internal precision, as defined in the StabilityBranch.initiateSwap() natspec
+ UD60x18 swapAmountX18 = Math.convertTokenAmountToUd60x18(6, swapAmount);
+ UD60x18 assetPriceX18 = IPriceAdapter(fuzzVaultConfig.priceAdapter).getPrice();
+ UD60x18 expectedAssetOutX18 = marketMakingEngine.getAmountOfAssetOut(8, swapAmountX18, assetPriceX18);
+
+ changePrank({ msgSender: users.naruto.account });
+ // END OF SETUP //
+
+ uint128[] memory vaultIds = new uint128[](1);
+ vaultIds[0] = fuzzVaultConfig.vaultId;
+ uint128[] memory amountsIn = new uint128[](1);
+ amountsIn[0] = uint128(swapAmountX18.intoUint256());
+ uint128[] memory minAmountsOut = new uint128[](1);
+
+ // Ensure that there is enough assets in the vault to fulfill the swap.
+ assertGt(vaultAssetBalance, Math.convertUd60x18ToTokenAmount(6, expectedAssetOutX18));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ Errors.InsufficientVaultBalance.selector,
+ 8,
+ vaultAssetBalance,
+ expectedAssetOutX18.intoUint256()
+ ));
+ // However initiateSwap will revert with InsufficientVaultBalance
+ marketMakingEngine.initiateSwap(vaultIds, amountsIn, minAmountsOut);
+ }--
+
function testFuzz_RevertWhen_SecondVaultHasNoCollateral(
uint128 firstVaultId,
uint128 secondVaultId

Impact

Valid calls to StabilityBranch.initiateSwap() will revert due to the aforementioned check in vaults with underlying assets that have low decimals. This prevents StabilityBranch from being used with certain vaults.

Tools Used

Manual Review

Recommended Mitigation

Consider converting ctx.vaultAssetBalance to Zaros' internal precision (18 decimals) before checking whether (ctx.vaultAssetBalance < ctx.expectedAssetOut).

Updates

Lead Judging Commences

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

Decimal Precision Mismatch Causing Incorrect Swap Reverts in `StabilityBranch.initiateSwap()`

Appeal created

bigsam Auditor
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Decimal Precision Mismatch Causing Incorrect Swap Reverts in `StabilityBranch.initiateSwap()`

Support

FAQs

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

Give us feedback!