RebateFi Hook

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

Logic Error in _beforeInitialize() - Pool validation always fails

Root + Impact

Description

  • The _beforeInitialize hook is designed to ensure that the ReFi token must be present in every pool as either currency0 or currency1 before the pool can be initialized. This validates that all pools using this hook include ReFi as one of the trading pair currencies.

  • The validation logic incorrectly checks key.currency1 twice instead of checking both key.currency0 and key.currency1. This causes the function to only accept pools where ReFi is specifically in the currency1 position, rejecting valid pools where ReFi is in the currency0 position.

function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
if (Currency.unwrap(key.currency1) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) { // check the same thing twice.
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}

Risk

Likelihood:

  • Approximately 50% of potential trading pairs will place ReFi in the currency0 position based on address sorting, making this issue occur frequently

  • Users attempting to create pools with ReFi as the lower address token will always encounter reverts

Impact:

  • Legitimate pools (ReFi/USDC where ReFi's address < USDC's address) cannot be created

  • Users and liquidity providers cannot access intended functionality, blocking protocol adoption

Proof of Concept

This PoC is showing how the vulnerability can be provoked.

// Assuming that the ReFi token address: 0x1111111111111111111111111111111111111111
// Assuming that the USDC token address: 0x2222222222222222222222222222222222222222
// ReFi address < USDC address (sorted as currency0)
PoolKey memory key = PoolKey({
currency0: Currency.wrap(0x1111111111111111111111111111111111111111), // ReFi
currency1: Currency.wrap(0x2222222222222222222222222222222222222222), // USDC
fee: 3000,
tickSpacing: 60,
hooks: IHooks(address(this))
});
// Call _beforeInitialize
// Check: Currency.unwrap(key.currency1) != ReFi
// => 0x2222... != 0x1111... => TRUE
// Check: Currency.unwrap(key.currency1) != ReFi
// => 0x2222... != 0x1111... => TRUE
// Result: TRUE && TRUE = TRUE => reverts...
// This should pass because ReFi is in the pool as currency0 but it reverts
// This would work : ReFi address > WETH address (sorted as currency1)
PoolKey memory key2 = PoolKey({
currency0: Currency.wrap(0x0000000000000000000000000000000000000000), // WETH (lower address)
currency1: Currency.wrap(0x1111111111111111111111111111111111111111), // ReFi
fee: 3000,
tickSpacing: 60,
hooks: IHooks(address(this))
});
// Call _beforeInitialize
// Check: Currency.unwrap(key2.currency1) != ReFi
// => 0x1111... != 0x1111... => FALSE
// Check: Currency.unwrap(key2.currency1) != ReFi
// => 0x1111... != 0x1111... => FALSE
// Result: FALSE && FALSE = FALSE => good.
// This works correctly

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) {
+ if (Currency.unwrap(key.currency0) != ReFi &&
+ Currency.unwrap(key.currency1) != ReFi) {
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}
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!