Part 2

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

Incorrect Order of Fee Deduction in Swaps in BaseAdapter.sol

Summary

/// @param assetAmount in native precision of asset token
/// @return usdcOut in native usdc precision
function _convertAssetsToUsdc(
uint128 dexSwapStrategyId,
address asset,
uint256 assetAmount,
bytes memory path,
address recipient,
address usdc
)
internal
returns (uint256 usdcOut)
{
// revert if the amount is zero
if (assetAmount == 0) revert Errors.AssetAmountIsZero(asset);
// if the asset being handled is usdc, simply output it to `usdcOut`
if (asset == usdc) {
usdcOut = assetAmount;
} else {
// approve the asset to be spent by the dex adapter contract
DexSwapStrategy.Data storage dexSwapStrategy = DexSwapStrategy.loadExisting(dexSwapStrategyId);
IERC20(asset).approve(dexSwapStrategy.dexAdapter, assetAmount);
// verify if the swap should be input single or multihop
if (path.length == 0) {
// prepare the data for executing the swap
SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: asset,
tokenOut: usdc,
amountIn: assetAmount,
recipient: recipient
});
// swap the credit deposit assets for USDC and store the output amount
usdcOut = dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
} else {
// prepare the data for executing the swap
SwapExactInputPayload memory swapCallData = SwapExactInputPayload({
path: path,
tokenIn: asset,
tokenOut: usdc,
amountIn: assetAmount,
recipient: recipient
});
// swap the credit deposit assets for USDC and store the output amount
usdcOut = dexSwapStrategy.executeSwapExactInput(swapCallData);
}
// load market making config
MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
MarketMakingEngineConfiguration.load();
// cache the settlement base fee value using usdc's native decimals
uint256 settlementBaseFeeUsd = Collateral.load(usdc).convertUd60x18ToTokenAmount(
ud60x18(marketMakingEngineConfiguration.settlementBaseFeeUsdX18)
);
if (settlementBaseFeeUsd > 0) {
// revert if there isn't enough usdc to cover the base fee
// NOTE: keepers must be configured to buy good chunks of usdc at minimum (e.g $500)
// as the settlement base fee shouldn't be much greater than $1.
if (usdcOut < settlementBaseFeeUsd) {
revert Errors.FailedToPaySettlementBaseFee();
}
usdcOut -= settlementBaseFeeUsd;
// distribute the base fee to protocol fee recipients
marketMakingEngineConfiguration.distributeProtocolAssetReward(usdc, settlementBaseFeeUsd);
}
}
}

The BaseAdapter contract incorrectly deducts fees after swap execution, making it vulnerable to price manipulation and execution failures. This violates the CEI (Checks-Effects-Interactions) pattern, leading to potential fund loss and failed swaps due to insufficient balance.

Vulnerability Details

Fee is deducted AFTER the swap executes, meaning an attacker can manipulate the swap to reduce usdcOut.

If usdcOut < settlementBaseFeeUsd, the function fails after execution, causing a denial of service (DoS).

A scenario which exploits this :

Attacker manipulates market conditions before the swap executes (e.g., front-running the trade).

  • Swap executes with manipulated pricing, resulting in usdcOut being lower than expected.

  • Settlement fee deduction happens after execution:

    • If usdcOut < settlementBaseFeeUsd, the transaction fails.

    • If no failure occurs, the attacker benefits from avoiding fees while draining liquidity.

Impact

Failed swaps leading to DoS (Denial of Service)

Loss of protocol revenue

Manipulated Swaps

Potential Insolvency

Tools Used

Manual Review

Recommendations

Deduct the fee BEFORE swapping.

Updates

Lead Judging Commences

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

Support

FAQs

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