Part 2

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

Incorrect WETH reward distribution due to unchecked final token in multi-hop swap path

Summary

The _performMultiDexSwap function does not verify that the last token in the swap path is WETH before proceeding with reward distribution. This flaw could cause the reward distribution function to add the wrong amount of WETH in the receiveWethReward, potentially leading to incorrect reward calculations and distribution.

Vulnerability Details

The vulnerability exists in the following code in the convertAccumulatedFeesToWeth function:
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/branches/FeeDistributionBranch.sol#L188-L220

if (swapPath.enabled) {
ctx.tokensSwapped = _performMultiDexSwap(swapPath, ctx.assetAmount);
} else if (path.length == 0) {
// loads the dex swap strategy data storage pointer
DexSwapStrategy.Data storage dexSwapStrategy = DexSwapStrategy.loadExisting(dexSwapStrategyId);
// approve the collateral token to the dex adapter
IERC20(asset).approve(dexSwapStrategy.dexAdapter, ctx.assetAmount);
// prepare the data for executing the swap
SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: asset,
tokenOut: ctx.weth,
amountIn: ctx.assetAmount,
recipient: address(this)
});
// Swap collected collateral fee amount for WETH and store the obtained amount
ctx.tokensSwapped = dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
} else {
// loads the dex swap strategy data storage pointer
DexSwapStrategy.Data storage dexSwapStrategy = DexSwapStrategy.loadExisting(dexSwapStrategyId);
// approve the collateral token to the dex adapter
IERC20(asset).approve(dexSwapStrategy.dexAdapter, ctx.assetAmount);
// prepare the data for executing the swap
SwapExactInputPayload memory swapCallData = SwapExactInputPayload({
path: path,
tokenIn: asset,
tokenOut: ctx.weth,
amountIn: ctx.assetAmount,
recipient: address(this)
});

In both the else if and else branches, the code ensures that tokenOut will always be WETH (tokenOut: ctx.weth). However, the _performMultiDexSwap function does not verify if the last token (tokenOut) in the swap path is WETH before swaps (tokenOut: assets[i + 1]).
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/branches/FeeDistributionBranch.sol#L428

SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: assets[i],
tokenOut: assets[i + 1],
amountIn: amountIn,
recipient: address(this)
});

However, after performing the swaps, the system assumes the final output token is WETH, and the receiveWethRewar is calculated based on this assumption but the address does not receive WETH. Also, the receiveWethReward assumes the self.receivedFees has been fully swapped to weth and remove the asset.

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/Market.sol#L504-L525

function receiveWethReward(
Data storage self,
address asset,
UD60x18 receivedProtocolWethRewardX18,
UD60x18 receivedVaultsWethRewardX18
)
internal
{
// if a market credit deposit asset has been used to acquire the received weth, we need to reset its balance
if (asset != address(0)) {
// removes the given asset from the received market fees enumerable map as we assume it's been fully
// swapped to weth
self.receivedFees.remove(asset);
}
// increment the amount of pending weth reward to be distributed to fee recipients
self.availableProtocolWethReward =
ud60x18(self.availableProtocolWethReward).add(receivedProtocolWethRewardX18).intoUint128();
// increment the all time weth reward storage
self.wethRewardPerVaultShare =
ud60x18(self.wethRewardPerVaultShare).add(receivedVaultsWethRewardX18).intoUint128();

Impact

Incorrect receiveWethReward: If the final token in the swap path is not WETH, the system will incorrectly adding WETH rewards without actually receving the WETH. Leading to imbalances in reward distribution.

Tools Used

Manual code review

Recommended Mitigation

  1. Add a check for WETH in final swap path token in the _performMultiDexSwap function

if (assets[assets.length - 1] != ctx.weth) {
revert Errors.InvalidSwapPath(assets[assets.length - 1], ctx.weth);
}
  1. Ensure the fee has been swapped to WETH before self.receivedFees.remove(asset); in receiveWethReward function

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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