RebateFi Hook

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

ReFi Token Validation Logic Error Allows Non-ReFi Pools to Initialize

Root + Impact

Description

  • Normal Behavior: The _beforeInitialize function should validate that the ReFi token is present in the pool by checking if either currency0 or currency1 matches the ReFi token address.

  • Specific Issue: The validation logic in _beforeInitialize contains a critical copy-paste error where it checks currency1 twice instead of checking both currency0 and currency1:

// 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) { // Should be currency0, not currency1
revert ReFiNotInPool();
}
return BaseHook.beforeInitialize.selector;
}

Risk

Likelihood:

  • This will occur every time a pool is initialized where ReFi is currency0 but not currency1

  • The condition currency1 != ReFi && currency1 != ReFi is always equivalent to currency1 != ReFi, making the check incomplete

  • Pools with ReFi as currency0 will incorrectly revert with ReFiNotInPool() error

  • Pools without ReFi token at all may incorrectly pass validation if they happen to have ReFi as currency1

Impact:

  • Hook cannot be used with pools where ReFi is currency0 (50% of valid pool configurations are broken)

  • Asymmetric fee logic may be applied to wrong pools if ReFi happens to be currency1 in a pool where it shouldn't be

  • Protocol functionality is severely limited or completely broken depending on pool configuration

  • Users cannot create valid ReFi/ETH pools in the intended configuration

Proof of Concept

// Scenario 1: ReFi as currency0 - Will incorrectly REVERT
PoolKey memory key1 = PoolKey({
currency0: Currency.wrap(address(reFiToken)), // ReFi is currency0
currency1: Currency.wrap(address(otherToken)),
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 60,
hooks: IHooks(address(rebateHook))
});
// This will revert with ReFiNotInPool() even though ReFi IS in the pool
// Scenario 2: ReFi as currency1 - Will correctly PASS
PoolKey memory key2 = PoolKey({
currency0: Currency.wrap(address(otherToken)),
currency1: Currency.wrap(address(reFiToken)), // ReFi is currency1
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 60,
hooks: IHooks(address(rebateHook))
});
// This will pass validation 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!