RebateFi Hook

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

`_isReFiBuy` Function Leads to Incorrect LP Fee Collection

_isReFiBuy Function Leads to Incorrect LP Fee Collection

Description

  • In normal ReFi token transactions, the hook contract should apply different fee rates based on the transaction direction: 0% fee for buying ReFi to encourage accumulation, and 0.3% fee for selling ReFi to discourage selling.

  • Due to a logical error in the _isReFiBuy function, the buy and sell directions are completely reversed, resulting in actual fee collection that is the opposite of the expected economic model.

function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
if (IsReFiCurrency0) {
@> return zeroForOne; // ❌ Error: When ReFi is currency0, zeroForOne=true indicates selling ReFi
} else {
@> return !zeroForOne; // ❌ Error: When ReFi is currency1, zeroForOne=false indicates selling ReFi
}
}

Risk

Likelihood:

  • This erroneous logic is triggered every time a user executes a ReFi token transaction.

  • All pools using this hook contract will continuously be affected by the incorrect fee strategy.

  • The incorrect direction judgment cannot be fixed through simple configuration after contract deployment and requires redeployment.

Impact:

  • The economic model is completely ineffective: high fees are actually charged for buying behavior, while zero fees are provided for selling behavior.

  • User behavior is incorrectly incentivized: encouraging users to sell ReFi tokens instead of accumulating, which contradicts the goal of regenerative finance.

  • Protocol revenue loss: Due to charging fees in the wrong direction, the actual fee income may be much lower than expected.

  • User trust damage: The user experience does not match the advertised economic incentives, which may cause community dissatisfaction and trust crisis.

Proof of Concept

  • Add the function test__beforeSwap_Wrong_isReFiBuy in RebateFiHookTest.t.sol as follows:

function test__beforeSwap_Wrong_isReFiBuy() public {
uint256 usingAmount;
SwapParams memory swapParams;
PoolSwapTest.TestSettings memory testSettings;
vm.startPrank(user1);
// User performs ETH -> ReFi
vm.deal(user1, 1 ether);
usingAmount = 0.01 ether;
swapParams = SwapParams({
zeroForOne: true, // ETH -> ReFi
amountSpecified: -int256(usingAmount),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
testSettings = PoolSwapTest.TestSettings({
takeClaims: false,
settleUsingBurn: false
});
vm.expectEmit(true, false, false, false);
emit ReFiSwapRebateHook.ReFiSold(address(swapRouter), 0, 0);
// Perform swap
swapRouter.swap{value: usingAmount}(key, swapParams, testSettings, ZERO_BYTES);
console.log("SwapParams ETH -> ReFi, But emit ReFiSwapRebateHook.ReFiSold");
vm.stopPrank();
////////////////////////////////////////////////////////////
vm.startPrank(user2);
// User performs ReFi -> ETH
reFiToken.approve(address(swapRouter), type(uint256).max);
usingAmount = 0.01 ether;
swapParams = SwapParams({
zeroForOne: false, // ReFi -> ETH
amountSpecified: -int256(usingAmount),
sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
});
testSettings = PoolSwapTest.TestSettings({
takeClaims: false,
settleUsingBurn: false
});
vm.expectEmit(true, false, false, false);
emit ReFiSwapRebateHook.ReFiBought(address(swapRouter), 0);
// Perform swap
swapRouter.swap(key, swapParams, testSettings, ZERO_BYTES);
console.log("SwapParams ReFi -> ETH, But emit ReFiSwapRebateHook.ReFiBought");
vm.stopPrank();
}
  • Run: forge test --mt test__beforeSwap_Wrong_isReFiBuy -vv

  • Console output:

Ran 1 test for test/RebateFiHookTest.t.sol:TestReFiSwapRebateHook
[PASS] test__beforeSwap_Wrong_isReFiBuy() (gas: 330030)
Logs:
SwapParams ETH -> ReFi, But emit ReFiSwapRebateHook.ReFiSold
SwapParams ReFi -> ETH, But emit ReFiSwapRebateHook.ReFiBought
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 13.87ms (927.30µs CPU time)

Recommended Mitigation

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 12 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!