RebateFi Hook

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

ReFi Token Pool Initialization Restricted to `currency1` Position Due to Typo in `_beforeInitialize`

Root + Impact :High

The hook enforces that the designated ReFi token must be one of the two tokens in a pool during initialization. Due to a duplicated condition checking only key.currency1, pools where ReFi is currency0 are erroneously rejected — breaking expected protocol functionality and limiting deployment flexibility.`

Description

  • The _beforeInitialize hook is intended to validate that the ReFi token is present in the pool — either as currency0 or currency1 — and revert otherwise, ensuring the hook is only used on valid pools.

  • However, the condition Currency.unwrap(key.currency1) != ReFi && Currency.unwrap(key.currency1) != ReFi mistakenly repeats the check for key.currency1 and omits key.currency0 entirely, effectively requiring ReFi to be exclusively currency1.

// Root cause in the codebase with @> marks to highlight the relevant section
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;
}

Risk

Likelihood:

  • Any attempt to initialize a pool where ReFi is the first token (currency0, e.g., ReFi/WETH) will trigger the ReFiNotInPool() revert — this occurs deterministically for this common pool ordering.

  • Uniswap V4 pool keys are typically constructed with tokens sorted by address (lower address first), meaning depending on the ReFi token address, it may always be currency0, making successful initialization impossible without external workarounds.


Impact:

  • Protocol deployment failure: Owners cannot create pools with natural token ordering (e.g., ReFi/USDC if ReFi.address < USDC.address), violating expected behavior and documentation.

  • Loss of composability: Integrators (e.g., frontends, aggregators, other protocols) expecting standard pool key conventions will fail to use the hook, harming adoption and usability.

Proof of Concept

/// @notice: In this test we are going to check for the hook deployment when REFI token address is currency0 or currency1
///@dev: Lets create a file named RefiHookTest.t.sol in the tests folder and run these tests.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import "./ReFiSwapRebateHook.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {Currency} from "v4-core/types/Currency.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
contract ReFiHookTest is Test {
/// @notice: In this test we are going to check for the hook deployment when REFI token address is currency0 or currency1
///@dev: Lets create a file named RefiHookTest.t.sol in the tests folder and run these tests.
ReFiSwapRebateHook hook;
address poolManager = address(0x123); // mock
address constant REFI = address(0x1000);
address constant WETH = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // real WETH, REFI
PoolKey memory key2 = PoolKey({
currency0: Currency.wrap(REFI), // ← ReFi is currency0
currency1: Currency.wrap(highAddrToken),
fee: 3000 | LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 60,
hooks: Hooks.encodeHookPermissions(hook.getHookPermissions())
});
// ❌ This should pass (ReFi is in pool), but currently reverts!
hook._beforeInitialize(address(0), key2, 0); // ← Expect failure (test will pass only if revert occurs)
}
}

Recommended Mitigation

- remove this codea
@dev, lets look into the _beforeInitialize function, and rectify the if condition as well as creating a better naming convention for the different currencies.
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;
}
+ add this code
function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
address currency0 = Currency.unwrap(key.currency0);
address currency1 = Currency.unwrap(key.currency1);
if (currency0 != ReFi && currency1 != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}
Updates

Lead Judging Commences

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

Faulty pool check; only checks currency1 twice, omitting currency0.

Support

FAQs

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

Give us feedback!