RebateFi Hook

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

_beforeInitialize Always Reverts When ReFi is Token0 Due to Duplicate Currency Check

The _beforeInitialize hook is designed to enforce a security rule: only pools that contain the ReFi token as one of the two traded assets are allowed to use this hook. This prevents the hook from being accidentally (or maliciously) attached to unrelated pools.
The current implementation contains a critical logic error: it checks key.currency1 twice instead of checking both currency0 and currency1. As a result, the validation completely ignores whether the ReFi token is present as currency0. Whenever the ReFi token has the lower address (and is therefore sorted into currency0), the condition always evaluates to true and reverts, even though the pool is perfectly valifunction _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
// @> BUG: currency1 is checked twice – currency0 is never examined
if (Currency.unwrap(key.currency1) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}

function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
// @> BUG: currency1 is checked twice – currency0 is never examined
if (Currency.unwrap(key.currency1) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}
https://github.com/CodeHawks-Contests/2025-11-rebatefi-hook/blob/add4b298d1246ad2f1df726216849c1c31f83065/src/RebateFiHook.sol#L122-124

}

Risk

Likelihood:

Occurs on every pool initialization attempt

  • Triggers whenever ReFi token address < paired token address

Impact:

Hook deployment fails for half of all possible token orderings

  • Users/owners cannot create valid ReFi pools with standard token ordering

Example addresses
address constant ReFi = 0x0000000000000000000000000000000000000011; // lower address
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // higher address
// PoolManager will sort tokens → currency0 = ReFi, currency1 = WETH
PoolKey memory key = PoolKey({
currency0: Currency.wrap(ReFi),
currency1: Currency.wrap(WETH),
fee: uint24(0x800000), // dynamic fee placeholder
tickSpacing: 60,
hooks: IHooks(address(hook))
});
// Current buggy code evaluates:
// key.currency1 != ReFi → true (WETH != ReFi)
// key.currency1 != ReFi → true (again)
// → true && true → revert ReFiNotInPool()
// Even though ReFi IS in the pool → initialization permanently blocked

Recommended Mitigation

- remove this function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
- if (Currency.unwrap(key.currency1) != ReFi &&
- Currency.unwrap(key.currency1) != ReFi) {
+ // Ensure the ReFi token is one of the two currencies in the pool
+ if (Currency.unwrap(key.currency0) != ReFi &&
+ Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}ode
+ + bool hasReFi = Currency.unwrap(key.currency0) == ReFi ||
+ Currency.unwrap(key.currency1) == ReFi;
+ if (!hasReFi) revert ReFiNotInPool();
Updates

Lead Judging Commences

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