RebateFi Hook

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

Inverted Buy/Sell Logic in `_isReFiBuy`

Description:
The _isReFiBuy function's logic for buy/sell direction is inverted, making the protocol apply the wrong fee structure (charging buyers instead of sellers and vice versa).

Impact:
Fee logic is reversed: buyers may pay high (or full) fees while sellers avoid fees, completely defeating the protocol's intended economic incentives.

Proof of Concept:

// Mirror hook events so we can use expectEmit
event ReFiBought(address indexed buyer, uint256 amount);
event ReFiSold(address indexed seller, uint256 amount, uint256 fee);
function test_InvertedBuySellLogic_isReFiBuy() public {
// ----------------- BUY PATH: ETH -> ReFi -----------------
uint256 ethAmount = 0.01 ether;
// Fund user1 with ETH
vm.deal(user1, 1 ether);
vm.startPrank(user1);
// ETH -> ReFi (currency0 -> currency1) is logically a "buy"
SwapParams memory buyParams = SwapParams({
zeroForOne: true, // ETH -> ReFi
amountSpecified: -int256(ethAmount),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({
takeClaims: false,
settleUsingBurn: false
});
// Due to inverted _isReFiBuy logic with ReFi as currency1,
// the hook classifies this as a "sell" and emits ReFiSold
// instead of ReFiBought.
vm.expectEmit(true, false, false, false, address(rebateHook));
emit ReFiSold(user1, ethAmount, 0); // only check event type + indexed seller
swapRouter.swap{value: ethAmount}(key, buyParams, testSettings, ZERO_BYTES);
vm.stopPrank();
// ----------------- SELL PATH: ReFi -> ETH -----------------
uint256 reFiAmount = 0.01 ether;
vm.startPrank(user1);
reFiToken.approve(address(swapRouter), type(uint256).max);
// ReFi -> ETH (currency1 -> currency0) is logically a "sell"
SwapParams memory sellParams = SwapParams({
zeroForOne: false, // ReFi -> ETH
amountSpecified: -int256(reFiAmount),
sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
});
// Due to inverted _isReFiBuy logic, this path is treated as a "buy"
// and emits ReFiBought instead of ReFiSold.
vm.expectEmit(true, false, false, false, address(rebateHook));
emit ReFiBought(user1, reFiAmount); // only check event type + indexed buyer
swapRouter.swap(key, sellParams, testSettings, ZERO_BYTES);
vm.stopPrank();
}

Mitigation:
Invert the logic to match Uniswap's swap conventions:

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

Lead Judging Commences

chaossr Lead Judge 10 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!