Part 2

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

Discrepancy between actual USDC balance and vault state due to incorrect `usdDelta` calculation in `CreditDelegationBranch.sol#rebalanceVaultsAssets()`

Summary

A critical accounting discrepancy exists in CreditDelegationBranch.sol where the rebalanceVaultsAssets() function uses estimated USDC amounts instead of actual received amounts when updating vault states after swaps. This leads to inaccurate protocol accounting and potential misalignment between internal state and real USDC balances.

The issue stems from using pre-swap price estimations rather than actual swap output values when calculating usdDelta. Over time, this discrepancy could accumulate and cause significant protocol state inconsistencies, impacting vault accounting accuracy.

Links to affected code

  • Credit DelegationBranch.sol:L646~666

  • BaseAdapter.sol:L95~123

Vulnerability details

Finding description and impact

In the CreditDelegationBranch.sol#rebalanceVaultsAssets() function, there is a critical issue with how the usdDelta variable is calculated after performing an asset swap.

The function performs the following key operations:

  1. Calculates assetInputNative through getExpectedOutput() (L646)

  2. Executes swap using executeSwapExactInputSingle() (L663)

  3. Sets usdDelta directly from depositAmountUsdX18

The core issue lies in the fact that while the swap is executed in "exact input" mode, the function ignores the actual USDC amount received from the swap and instead uses the pre-swap estimation for updating the vault states.

This discrepancy leads to:

  • Incorrect depositedUsdc values in both vaults

  • Inaccurate marketsRealizedDebtUsd state

  • Misalignment between the protocol's internal accounting and actual USDC balances

The severity is high because this accounting mismatch could accumulate over time and lead to significant protocol state inconsistencies.

Technical Details

The getExpectedOutput() function in BaseAdapter.sol calculates expected outputs based on price feeds:

function getExpectedOutput(
address tokenIn,
address tokenOut,
uint256 amountIn
)
public
view
returns (uint256 expectedAmountOut)
{
// fail fast for zero input
if (amountIn == 0) revert Errors.ZeroExpectedSwapOutput();
// get token prices
UD60x18 priceTokenInX18 = IPriceAdapter(swapAssetConfigData[tokenIn].priceAdapter).getPrice();
UD60x18 priceTokenOutX18 = IPriceAdapter(swapAssetConfigData[tokenOut].priceAdapter).getPrice();
// convert input amount from native to internal zaros precision
UD60x18 amountInX18 = Math.convertTokenAmountToUd60x18(swapAssetConfigData[tokenIn].decimals, amountIn);
// calculate the expected amount out in native precision of output token
expectedAmountOut = Math.convertUd60x18ToTokenAmount(
swapAssetConfigData[tokenOut].decimals, amountInX18.mul(priceTokenInX18).div(priceTokenOutX18)
);
// revert when calculated expected output is zero; must revert here
// otherwise the subsequent slippage bps calculation will also
// return a minimum swap output of zero giving away the input tokens
if (expectedAmountOut == 0) revert Errors.ZeroExpectedSwapOutput();
}

However, due to factors like slippage and price impact, the actual received USDC amount will differ from this estimation.

Recommended Mitigation Steps

Two potential solutions:

  1. Use actual swap output to update vault states:
    In rebalanceVaultsAssets() function....

// Before
- dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
- uint128 usdDelta = depositAmountUsdX18.intoUint256().toUint128();
// After
+ uint256 amountOut = dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
+ uint128 usdDelta = Collateral.load(usdc).convertTokenAmountToSd59x18(amountOut);
  1. Modify the swap execution to use exact output mode instead, ensuring the received USDC matches the expected amount.

The first solution is recommended as it provides better accuracy and transparency in vault accounting.

Updates

Lead Judging Commences

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

`CreditDelegationBranch::rebalanceVaultsAssets` doesn't take DEX swap slippage into consideration when swapping debt vault's collateral asset to credit vault's usdc

he rebalanceVaultsAssets function in CreditDelegationBranch.sol updates vault accounting using the pre-swap USD value (usdDelta) rather than the actual post-swap USDC amount received. This means slippage is not accounted for, causing accounting misalignment - if there's negative slippage, the credit vault gets credited more USDC than actually received; if there's positive slippage, it gets credited less.

Support

FAQs

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