RebateFi Hook

First Flight #53
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Missing Validation for Zero-Amount Swaps Allows Unintended Hook Execution

Description

In the _beforeSwap function of RebateFiHook.sol, the hook derives the swap amount using:

uint256 swapAmount = params.amountSpecified < 0
? uint256(-params.amountSpecified)
: uint256(params.amountSpecified);

This assumes amountSpecified != 0.

However, in Uniswap V4, swap() calls with amountSpecified == 0 are valid and used for:

  • oracle updates

  • triggering hooks

  • dry runs / sandwich protection

  • internal router operations

If a user calls swap with amountSpecified = 0, the hook:

  1. Treats the swap as a buy or sell (even though it's neither)

  2. Emits ReFiBought or ReFiSold events with amount = 0

  3. Applies buyFee or sellFee override (even though no swap occurred)

  4. Consumes gas and changes user-facing analytics

  5. May cause systems depending on events (rebates, rewards, airdrops) to mis-execute

This contradicts expected Uniswap V4 hook behavior, where no swap should mean no side effects.


Impact

Unexpected hook execution, including:

  • Incorrect swap events with zero volume

  • Gas and execution overhead

  • Wrong analytics, misleading swap tracking

  • Potential reward system manipulation (fake swap events)

  • Fee override applied where it should not be

  • Possible griefing vector: attacker can spam swap(0) and distort data

Although no funds are lost directly, this introduces correctness risks and ecosystem-wide data corruption.

Most severe scenario:
A protocol relying on ReFiBought / ReFiSold events for rewards or rebates can be exploited with zero-cost swaps.


Proof of Concept

function test_ZeroAmountSwapTriggersHook() public {
// amountSpecified = 0 means no swap is happening
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: 0,
sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
});
// Expect the hook to emit an event (bug)
vm.expectEmit(true, true, true, true);
emit ReFiSwapRebateHook.ReFiBought(address(router), 0);
router.swap(poolKey, params, settings, "");
}

Executing the above emits a buy or sell event despite no swap.


Recommended Mitigation

Add a guard clause early in _beforeSwap:

if (params.amountSpecified == 0) {
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
LPFeeLibrary.KEEP_POOL_FEE // do not override fee
);
}
Updates

Lead Judging Commences

chaossr Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!