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.
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.
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;
}
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";
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);
}
}
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;
}