RebateFi Hook

First Flight #53
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

ReFi sells misclassified as buys, letting dumpers dodge the premium fee

Root + Impact

Description

  • Normal behavior: When users buy the designated ReFi token, the hook should apply buyFee (typically 0%) so buys are subsidized; when users sell ReFi, it should apply sellFee (e.g., 0.3%) to discourage dumping.

  • Actual behavior: _isReFiBuy misclassifies every swap whenever ReFi is currency0, so sells receive the zero-fee path and buys get penalized. This inverts the entire incentive model and lets dumpers avoid the premium fee.

// Root cause in the codebase with @> marks to highlight the relevant section
// src/RebateFiHook.sol
function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
@> if (IsReFiCurrency0) {
@> return zeroForOne; // BUG: direction is inverted
@> } else {
@> return !zeroForOne;
@> }
}

Risk

Likelihood

  • Any pool where ReFi is currency0 (roughly half of deployments) hits this bug immediately.

  • Users only need to perform normal Uniswap swaps; no special permissions or conditions are required.

Impact

  • Sellers never pay the configured premium fee, eliminating protocol revenue and removing the anti-dump mechanism.

  • Buyers are overcharged, discouraging accumulation and undermining the hook’s stated economic goal.

Proof of concept

Explanation: Deploy a pool with currency0 == ReFi, perform a standard sell swap (zeroForOne == true), and log the fee value returned by the hook—you’ll observe it equals buyFee instead of sellFee.

function test_Sell_BypassesFee_WhenReFiIsCurrency0() external {
vm.deal(user1, 1 ether);
vm.startPrank(user1);
reFiToken.approve(address(swapRouter), type(uint256).max);
SwapParams memory sell = SwapParams({
zeroForOne: true, // selling ReFi because it is currency0
amountSpecified: -int256(0.01 ether),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
uint24 fee = swapRouter.swap(key, sell, DEFAULT_SETTINGS, ZERO_BYTES);
assertEq(fee, rebateHook.buyFee(), "sell wrongly charged buyFee");
}

Recommended Mitigation

Explanation: Flip the logic so “buy” means the user receives ReFi; when ReFi is currency0, that happens only when zeroForOne == false.

function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
- if (IsReFiCurrency0) {
- return zeroForOne;
- } else {
- return !zeroForOne;
- }
+ if (IsReFiCurrency0) {
+ return !zeroForOne; // zeroForOne means selling currency0 (ReFi)
+ } else {
+ return zeroForOne; // zeroForOne means receiving currency1 (ReFi)
+ }
}
Updates

Lead Judging Commences

chaossr Lead Judge 11 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Inverted buy/sell logic when ReFi is currency0, leading to incorrect fee application.

Support

FAQs

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

Give us feedback!