RebateFi Hook

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

The `ReFiSwapRebateHook::_beforeInitialize` function check allow ReFi as currency 1 token only, preventing other tokens to be used as currency 1 token and currency 0 token to be ReFi token

ReFi must be one of the two pool tokens → Only ReFi as currency1 is accepted due to duplicated condition

Description

  • The purpose of _beforeInitialize in ReFiSwapRebateHook is to ensure that the ReFi token is present in every pool that uses this hook (so rebates can be paid in ReFi).

  • The current check contains a copy-paste error: it compares currency1 with ReFi twice, making the condition always true when currency1 is not ReFi and completely ignoring currency0. As a result, pools where ReFi is currency0 (the conventional ordering for most pairs) are rejected, while only pools with ReFi as currency1 are allowed.

// 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:

  • Every pool creation where ReFi is sorted as currency0 (the vast majority of pairs, because Uniswap V4 canonical ordering puts the lower-address token as currency0) triggers the revert

  • Any front-end or script that follows the standard token ordering will fail to create the pool

Impact:

  • Intended pools (e.g., USDC/ReFi, WETH/ReFi) cannot be initialized when ReFi has the higher address and therefore becomes currency1

  • The hook becomes effectively limited to only exotic pairs where ReFi is the lower-address token, severely restricting usability

  • Users and integrators waste time and gas on failed transactions

Proof of Concept

Add the following code snippet to the RebateFiHookTest.t.sol test file.

This snippet of code is to demonstrate that the pool will revert when trying to initialize the pool with ReFi token as currency 0 and ERC20 token as currency 1.

function test_ReFiNotInPool() public {
// Deploy the ReFi token
reFiToken = new MockERC20("ReFi Token", "ReFi", 18);
reFiCurrency = Currency.wrap(address(reFiToken));
// Deploy the ERC20 token
token = new MockERC20("TOKEN", "TKN", 18);
tokenCurrency = Currency.wrap(address(token));
// Get creation code for hook
bytes memory creationCode = type(ReFiSwapRebateHook).creationCode;
bytes memory constructorArgs = abi.encode(manager, address(reFiToken));
// Find a salt that produces a valid hook address
uint160 flags = uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG);
(address hookAddress, bytes32 salt) = HookMiner.find(address(this), flags, creationCode, constructorArgs);
// Deploy the hook with the mined salt
rebateHook = new ReFiSwapRebateHook{salt: salt}(manager, address(reFiToken));
require(address(rebateHook) == hookAddress, "Hook address mismatch");
// Expect revert when trying to initialize the pool with ReFi token as currency 0 and ERC20 token as currency 1
vm.expectRevert();
// Initialize the pool with ReFi token as currency 0 and ERC20 token as currency 1 using DYNAMIC_FEE_FLAG
(key,) = initPool(reFiCurrency, tokenCurrency, rebateHook, LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1_s);
}

Recommended Mitigation

Possible mitigation is to check if the currency 0 token is ReFi.

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.currency0) != 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!