RebateFi Hook

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

Audit of a Uniswap V4 hook implementation called the RebateFi Hook. The audit identifies two bugs a low and a potential high risk bug.

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • "beforeInitialize" compares currency1 twice


  • Root == function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
    bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi; The function returns the wrong boolean for buy/sell detection.

  • Impact == That inverts buy/sell logic and will apply the wrong fee directionally.Wrong direction ⇒ you charge buy fee on sells and vice versa — money lost / incentives inverted.

    if (IsReFiCurrency0) {
    //@> The func returns ZeroforOne instead of !zeroForone
    return zeroForOne;
    } else {
    return !zeroForOne;
    }
    }
// Root cause in the codebase with @> marks to highlight the relevant section
if (IsReFiCurrency0) {
//@> The func returns zeroforOne instead of !zeroForone
return zeroForOne;
} else {
//@> The func returns !zeroforOne instead of zeroForone
return zeroForOne;
return !zeroForOne;
}
}

Risk

Likelihood:

  • Reason 1 // The first currency (currency0) is never checked, so the pool could contain ReFi in currency0 and this will still revert.

  • Reason 2 // If ReFi is currency0, zeroForOne == true means swapping token0→token1 (selling token0), but your code returns zeroForOne and marks it as a buy.

Impact:

  • Impact 1 The if statement to check the currency before it initializes won't check the first currency (currency0)

  • Impact 2 Wrong direction => you charge buy fee on sells and vice versa -money lost/incentives inverted

Proof of Concept

  1. The code decides zeroForOne / “direction” incorrectly (or incompletely) because it doesn’t check which slot (token0/token1) ReFi occupies; that can lead to charging a buy fee on a sell or vice-versa.

  2. The first currency is not checked so the pool could contain ReFi in currency0 a nd will still revert.

    Correct approach

    ~Read token0 and token1 from the pool.

    ~Decide the direction with a direct comparison to the token the trader supplied (tokenIn or whatever your handler has).

    ~Given zeroForOne (true = token0→token1), classify sellRefi / buyRefi by checking which pool slot ReFi occupies

function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
if (Currency.unwrap(key.currency1) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}
//Function returns the wrong boolean.It is inverted may cause money lost / incentives inverted
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;
}
}

Recommended Mitigation

  1. To eliminate the incorrect fee direction and ensure the hook always applies the correct buy/sell logic regardless of whether ReFi is currency0 or currency1, add an explicit helper that determines whether the swap represents a ReFi buy.

  2. for the second bug, the function _isRefiBuy currently misidentifies swap direction by returning zeroForOne when Refi is currency0. This inverts buy/sell classification. If unaddressed, the protocol will apply incorrect fees, reward the wrong side of trades and expose itself to arbitrage and sandwich attacts. Replace the function with the corrected version that returns !zeroForOne when Refi is currency0 to ensure proper buy detection.

- remove this code
if (Currency.unwrap(key.currency1) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
+ add this code
if (Currency.unwrap(key.currency0) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
- remove this code
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;
}
}
+ add this code
function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
bool isReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
if (isReFiCurrency0) {
// if ReFi is currency0, swapping 0->1 (zeroForOne == true) = SELL ReFi
return !zeroForOne;
} else {
// if ReFi is currency1, swapping 0->1 (zeroForOne == true) = BUY ReFi
return zeroForOne;
}
}
Updates

Lead Judging Commences

chaossr Lead Judge
13 days ago
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!