RebateFi Hook

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

ReFi hook rejects pools when ReFi token is currency0

Root + Impact

Description

  • Uniswap v4 hooks typically ensure a target token is present in the pool by checking that either currency0 or currency1 matches the token's address during beforeInitialize.

  • RefiSwapRebateHook mistakenly compares currency1 twice, so deployments where the ReFi token sorts into currency0 revert with ReFiNotInPool() even though the token is present.

// Root cause in the codebase is in lines 122-126 of file src/RebateFiHook.sol
// Specifically with the function _beforeInitialize.
// @> is marked 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 pool where the ReFi token address is lower than its pair (e.g., another ERC-20 minted later) will have ReFi sorted into currency0; initialization then reverts immediately.

  • Integrators creating multiple pools with different quote tokens will inevitably encounter a token ordering where ReFi becomes currency0.

Impact:

  • Hook becomes undeployable for a large class of ReFi paris, preventing liquidity bootstrapping and contradicting expectations that ReFi can be paired with arbitrary tokens.

  • Operational overhead: deployments fail with ReFiNotInPool, forcing manual debugging or address reshuffling to bypass the issue.

Proof of Concept

// I would suggest to paste the following into test/RebateFiHookTest.t.sol (under initialization tests) to demonstrate the bug.
// Next run the command forge test --match-test test_BeforeInitialize_RevertsWhenReFiCurrency0
// You'll exercise the new PoC. reverts even though ReFi is in the pool.
// The hook reverts when ReFi sits in currency0 (in pool), proving the validation bug because _beforeInitialize checks currency1 twice
function test_BeforeInitialize_RevertsWhenReFiCurrency0() public {
MockERC20 higherToken = new MockERC20("HIGH", "HIGH", 18);
// Ensure ReFi sorts into currency0
require(address(reFiToken) < address(higherToken), "ordering assumption broken");
PoolKey memory manualKey = PoolKey({
currency0: Currency.wrap(address(reFiToken)),
currency1: Currency.wrap(address(higherToken)),
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 60,
hooks: rebateHook
});
vm.expectRevert(ReFiSwapRebateHook.ReFiNotInPool.selector);
vm.prank(address(manager));
rebateHook.beforeInitialize(address(this), manualKey, SQRT_PRICE_1_1_s);
}
// If the test fails, it means the hook threw something other than ReFiNotInPool (or didn’t revert at all). That indicates the bug isn’t reproduced—our crafted pool didn’t hit the faulty branch.
// When the test passes, it means the hook reverted exactly with ReFiNotInPool even though ReFi was in the pool as currency0. That proves the hook wrongly rejects valid pools and confirms the bug.

Recommended Mitigation

-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();
- }
+function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
+ if (
+ Currency.unwrap(key.currency0) != ReFi &&
+ Currency.unwrap(key.currency1) != ReFi
+ ) {
+ revert ReFiNotInPool();
+ }
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!