Summary
While tracking the TOTAL deposited usdc and the marketrealizedebtusd an error was made in utilizing the estimated and not the actual amount returned from the swap action this will lead to an imbalance in tracking the debt system and the accumulation of this can force the contract to become insolvent.
Vulnerability Details
The vault was balanced but the amount used in calculation is not the actual usdc value available hence this lead to wrong account and over inflation of the usdc token amount in the contract.
since we are swapping Vault asset to usdc we should use the returned usdc value.
function rebalanceVaultsAssets(uint128[2] calldata vaultsIds) external onlyRegisteredSystemKeepers {
Vault.Data storage inCreditVault = Vault.loadExisting(vaultsIds[0]);
Vault.Data storage inDebtVault = Vault.loadExisting(vaultsIds[1]);
if (inCreditVault.engine != inDebtVault.engine) {
revert Errors.VaultsConnectedToDifferentEngines();
}
uint256[] memory vaultsIdsForRecalculation = new uint256[]();
vaultsIdsForRecalculation[0] = vaultsIds[0];
vaultsIdsForRecalculation[1] = vaultsIds[1];
Vault.recalculateVaultsCreditCapacity(vaultsIdsForRecalculation);
SD59x18 inDebtVaultUnsettledRealizedDebtUsdX18 = inDebtVault.getUnsettledRealizedDebt();
SD59x18 inCreditVaultUnsettledRealizedDebtUsdX18 = inCreditVault.getUnsettledRealizedDebt();
if (
inCreditVaultUnsettledRealizedDebtUsdX18.lte(SD59x18_ZERO)
|| inDebtVaultUnsettledRealizedDebtUsdX18.gte(SD59x18_ZERO)
) {
revert Errors.InvalidVaultDebtSettlementRequest();
}
SD59x18 inDebtVaultUnsettledRealizedDebtUsdX18Abs = inDebtVaultUnsettledRealizedDebtUsdX18.abs();
SD59x18 depositAmountUsdX18 = inCreditVaultUnsettledRealizedDebtUsdX18.gt(
inDebtVaultUnsettledRealizedDebtUsdX18Abs
) ? inDebtVaultUnsettledRealizedDebtUsdX18Abs : inCreditVaultUnsettledRealizedDebtUsdX18;
DexSwapStrategy.Data storage dexSwapStrategy =
DexSwapStrategy.loadExisting(inDebtVault.swapStrategy.usdcDexSwapStrategyId);
address usdc = MarketMakingEngineConfiguration.load().usdc;
CalculateSwapContext memory ctx;
ctx.inDebtVaultCollateralAsset = inDebtVault.collateral.asset;
ctx.dexAdapter = dexSwapStrategy.dexAdapter;
@audit>> vault asset to swap>> uint256 assetInputNative = IDexAdapter(ctx.dexAdapter).getExpectedOutput(
usdc,
ctx.inDebtVaultCollateralAsset,
Collateral.load(usdc).convertSd59x18ToTokenAmount(depositAmountUsdX18)
);
SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: ctx.inDebtVaultCollateralAsset,
tokenOut: usdc,
amountIn: assetInputNative,
recipient: address(this)
});
IERC20(ctx.inDebtVaultCollateralAsset).approve(ctx.dexAdapter, assetInputNative);
@audit>>> returns swapped usdc amount>> dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
@audit>>> uint128 usdDelta = depositAmountUsdX18.intoUint256().toUint128();
@audit >> note
inCreditVault.depositedUsdc += usdDelta;
inCreditVault.marketsRealizedDebtUsd += usdDelta.toInt256().toInt128();
inDebtVault.depositedUsdc -= usdDelta;
inDebtVault.marketsRealizedDebtUsd -= usdDelta.toInt256().toInt128();
emit LogRebalanceVaultsAssets(vaultsIds[0], vaultsIds[1], usdDelta);
}
-
We try to rebalance the vaults as done in other functions
@audit>> vault asset to swap>> uint256 assetInputNative = IDexAdapter(ctx.dexAdapter).getExpectedOutput(
usdc,
ctx.inDebtVaultCollateralAsset,
Collateral.load(usdc).convertSd59x18ToTokenAmount(depositAmountUsdX18)
);
We confirmed the amount of vault assets needed to get the usdc amounts that we need.
2.We make a call and swap the native to usdc but note due to slippage the amount returned will be less than 1% or more than the targeted usdc amount. NOTE WE USE EXACTIN TO swap using exact out would have been good but with exact in.
The usdc amount is not equal to the targeted depositamountusdx18
SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: ctx.inDebtVaultCollateralAsset,
tokenOut: usdc,
amountIn: assetInputNative,
recipient: address(this)
});
IERC20(ctx.inDebtVaultCollateralAsset).approve(ctx.dexAdapter, assetInputNative);
dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
3.The swap calls the function below and because of slippage returns a value lesser than the depositamount
function executeSwapExactInputSingle(SwapExactInputSinglePayload calldata swapPayload)
external
returns (uint256 amountOut)
{
IERC20(swapPayload.tokenIn).transferFrom(msg.sender, address(this), swapPayload.amountIn);
address uniswapV2SwapStrategyRouterCache = uniswapV2SwapStrategyRouter;
IERC20(swapPayload.tokenIn).approve(uniswapV2SwapStrategyRouterCache, swapPayload.amountIn);
@audit>> uint256 expectedAmountOut = getExpectedOutput(swapPayload.tokenIn, swapPayload.tokenOut, swapPayload.amountIn);
@audit>> uint256 amountOutMinimum = calculateAmountOutMin(expectedAmountOut);
address[] memory path = new address[]();
path[0] = swapPayload.tokenIn;
path[1] = swapPayload.tokenOut;
@audit>> uint256[] memory amountsOut = IUniswapV2Router02(uniswapV2SwapStrategyRouterCache).swapExactTokensForTokens({
amountIn: swapPayload.amountIn,
amountOutMin: amountOutMinimum,
path: path,
to: swapPayload.recipient,
deadline: deadline
});
@audit>> return amountsOut[1];
}
4.minout
function calculateAmountOutMin(uint256 amountOutMinExpected) public view returns (uint256 amountOutMin) {
amountOutMin =
(amountOutMinExpected * (Constants.BPS_DENOMINATOR - slippageToleranceBps)) / Constants.BPS_DENOMINATOR;
}
Knowing this the amount swapped in will always be lesser but the contract incorrectly used depositamount in it accounting
@audit>> wrong.. use swap return>> uint128 usdDelta = depositAmountUsdX18.intoUint256().toUint128();
@audit>> wrong.. use swap return>> inCreditVault.depositedUsdc += usdDelta;
@audit>> wrong.. use swap return>> inCreditVault.marketsRealizedDebtUsd += usdDelta.toInt256().toInt128();
@audit>> wrong.. use swap return>> inDebtVault.depositedUsdc -= usdDelta;
@audit>> wrong.. use swap return>> inDebtVault.marketsRealizedDebtUsd -= usdDelta.toInt256().toInt128();
emit LogRebalanceVaultsAssets(vaultsIds[0], vaultsIds[1], usdDelta);
}
Comparing this error to other places where the implementation was correctly handled
settlevaultsdebts function
ctx.usdcOut = _convertAssetsToUsdc(
vault.swapStrategy.usdcDexSwapStrategyId,
ctx.vaultAsset,
ctx.swapAmount,
vault.swapStrategy.usdcDexSwapPath,
address(this),
ctx.usdc
);
if (ctx.usdcOut == 0) revert Errors.ZeroOutputTokens();
ctx.usdcOutX18 = usdcCollateralConfig.convertTokenAmountToUd60x18(ctx.usdcOut);
vault.marketsRealizedDebtUsd -= ctx.usdcOutX18.intoUint256().toInt256().toInt128();
UsdTokenSwapConfig.load().usdcAvailableForEngine[vault.engine] += ctx.usdcOutX18.intoUint256();
Impact
Wrong account tracking will lead to multiple accounting error and inaccurate tracking of many sensitive functions like position closing and liquidation.
Tools Used
Manual Review
Recommendations
Use the returned usdc from swap as this will accurately state the actual amount obtained to update the system.