RebateFi Hook

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

Incorrect Pool Validation in `ReFiSwapRebateHook::_beforeInitialize` Causes `ReFi` Pools to Fail When Token Is `currency0`

Root + Impact

Users attempting to initialize pools with `ReFi` positioned as `currency0` will always face transaction failures. This results in wasted gas costs, failed deployments, and frustration, as valid pools are consistently rejected. The deterministic nature of the bug means users cannot bypass or work around it, directly reducing confidence in the system and discouraging participation.
For the contract, the flawed validation logic prevents half of all legitimate pool configurations from being supported. This restricts flexibility, undermines interoperability with other protocols, and breaks a fundamental workflow in pool initialization. Because the error is guaranteed to occur whenever `ReFi` is in `currency0`, the issue imposes both functional and economic costs, making it a high‑severity vulnerability that impacts core usability and trust in the protocol.

Description

The `ReFiSwapRebateHook::_beforeInitialize` function is meant to ensure that the `ReFi` token is part of a pool before initialization. However, the condition mistakenly checks `currency1` twice and ignores `currency0`. This logic error means that any pool where `ReFi` is positioned as `currency0` will always revert, even though the pool is valid. Because pool initialization is a core function, this bug directly blocks half of all possible pool configurations, making the hook unusable in many legitimate scenarios. The issue is deterministic, easily triggered, and results in wasted gas, failed deployments, and restricted functionality. Its severity is high because it breaks expected behavior in a fundamental workflow and prevents the system from supporting valid pools.
// 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) {
if (Currency.unwrap(key.currency1) != ReFi && Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

Place the below code into the**RebateFiHookTest.t.sol**
The code demonstrates that RebateFiHook::_beforeInitialize incorrectly rejects pools whenever ReFi is positioned as currency1, causing valid pools to fail initialization and imposing high‑severity functional and economic costs.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Test} from "forge-std/Test.sol";
import {ReFiSwapRebateHook} from "../src/RebateFiHook.sol";
import {Currency, CurrencyLibrary} from "v4-core/types/Currency.sol";
import "./mocks/MockPoolManager.sol"; // path to your mock
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {IHooks} from "v4-core/interfaces/IHooks.sol";
contract TestRefiHook is Test {
ReFiSwapRebateHook public hook;
address public owner = address(1);
address public reFiToken = address(0x1234567890123456789012345678901234);
address public token = address(0x9876543210987654321098765432109876);
IPoolManager public poolManager = IPoolManager(address(0x1111111111111111111111111111111111));
function setUp() public {
vm.prank(owner);
hook = new ReFiSwapRebateHook(poolManager, reFiToken);
}
function testCurrencyValidation() public {
PoolKey memory validKey = PoolKey({
currency0: Currency.wrap(reFiToken),
currency1: Currency.wrap(token),
hooks: IHooks(address(hook)),
fee: 3000,
tickSpacing: 60
});
vm.prank(owner);
bytes4 selector = hook.beforeInitialize(address(0), validKey, 0);
assertEq(selector, hook.beforeInitialize.selector);
PoolKey memory invalidKey = PoolKey({
currency0: Currency.wrap(token),
currency1: Currency.wrap(reFiToken),
hooks: IHooks(address(hook)),
fee: 3000,
tickSpacing: 60
});
vm.expectRevert(bytes("ReFiNotInPool"));
vm.prank(owner);
hook.beforeInitialize(address(0), invalidKey, 0);
}
}

Recommended Mitigation

Correct the validation logic: Update the ReFiSwapRebateHook::_beforeInitialize function to check both currency0 and currency1 instead of checking currency1 twice.

function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
- if (Currency.unwrap(key.currency1) != ReFi && Currency.unwrap(key.currency1) != ReFi) {
+ if (Currency.unwrap(key.currency0) != ReFi && Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}
Updates

Lead Judging Commences

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