Summary
Wrong calculation of usdDelta
in CreditDelegationBranch.sol#rebalanceVaultsAssets()
will cause vaults' state to be different from real usd balance.
Vulnerability Details
CreditDelegationBranch.sol#rebalanceVaultsAssets()
function is as follows.
function rebalanceVaultsAssets(uint128[2] calldata vaultsIds) external onlyRegisteredSystemKeepers {
Vault.Data storage inCreditVault = Vault.loadExisting(vaultsIds[0]);
Vault.Data storage inDebtVault = Vault.loadExisting(vaultsIds[1]);
...
646 uint256 assetInputNative = IDexAdapter(ctx.dexAdapter).getExpectedOutput(
usdc,
ctx.inDebtVaultCollateralAsset,
Collateral.load(usdc).convertSd59x18ToTokenAmount(depositAmountUsdX18)
);
654 SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: ctx.inDebtVaultCollateralAsset,
tokenOut: usdc,
amountIn: assetInputNative,
recipient: address(this)
});
IERC20(ctx.inDebtVaultCollateralAsset).approve(ctx.dexAdapter, assetInputNative);
663 dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
uint128 usdDelta = depositAmountUsdX18.intoUint256().toUint128();
inCreditVault.depositedUsdc += usdDelta;
inCreditVault.marketsRealizedDebtUsd += usdDelta.toInt256().toInt128();
inDebtVault.depositedUsdc -= usdDelta;
inDebtVault.marketsRealizedDebtUsd -= usdDelta.toInt256().toInt128();
emit LogRebalanceVaultsAssets(vaultsIds[0], vaultsIds[1], usdDelta);
}
On L646, assetInputNative
is calculated as input amount for depositAmountUsdX18
as output amount through following code.
File: BaseAdapter.sol
function getExpectedOutput(
address tokenIn,
address tokenOut,
uint256 amountIn
)
public
view
returns (uint256 expectedAmountOut)
{
if (amountIn == 0) revert Errors.ZeroExpectedSwapOutput();
UD60x18 priceTokenInX18 = IPriceAdapter(swapAssetConfigData[tokenIn].priceAdapter).getPrice();
UD60x18 priceTokenOutX18 = IPriceAdapter(swapAssetConfigData[tokenOut].priceAdapter).getPrice();
UD60x18 amountInX18 = Math.convertTokenAmountToUd60x18(swapAssetConfigData[tokenIn].decimals, amountIn);
expectedAmountOut = Math.convertUd60x18ToTokenAmount(
swapAssetConfigData[tokenOut].decimals, amountInX18.mul(priceTokenInX18).div(priceTokenOutX18)
);
if (expectedAmountOut == 0) revert Errors.ZeroExpectedSwapOutput();
}
As we can see above, assetInputNative
is calculated from prices of asset and usdc.
And then on L654, swapCallData
is configured with assetInputNative
as input amount.
L663 swaps assets to usdc but its return amount is not used.
So usdDelta
is same as depositAmountUsd
even though asset is swapped through exact input
mode.
This is wrong.
The swapped output usdc amount is different from usdDelta.
This vulnerability causes vault's states such as depositedUsdc
and marketsRealizedDebtUsd
to be different from real balance.
This problem will cause protocol's broken state.
Impact
This vulnerability causes vault's states such as depositedUsdc
and marketsRealizedDebtUsd
to be different from real balance.
This problem will cause protocol's broken state.
Tools Used
Manual review
Recommendations
There can be two ways to mitigate this problem.
Modify code so that usdDelta is calculated from return value of swap function as follows.
function rebalanceVaultsAssets(uint128[2] calldata vaultsIds) external onlyRegisteredSystemKeepers {
Vault.Data storage inCreditVault = Vault.loadExisting(vaultsIds[0]);
Vault.Data storage inDebtVault = Vault.loadExisting(vaultsIds[1]);
...
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);
-- dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
++ uint256 amountOut = dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
-- uint128 usdDelta = depositAmountUsdX18.intoUint256().toUint128();
++ uint128 usdDelta = Collateral.load(usdc).convertTokenAmountToSd59x18(amountOut);
inCreditVault.depositedUsdc += usdDelta;
inCreditVault.marketsRealizedDebtUsd += usdDelta.toInt256().toInt128();
inDebtVault.depositedUsdc -= usdDelta;
inDebtVault.marketsRealizedDebtUsd -= usdDelta.toInt256().toInt128();
emit LogRebalanceVaultsAssets(vaultsIds[0], vaultsIds[1], usdDelta);
We can add logic so that assetInputNative is calculated though exact output
mode.