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.
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
function test_BeforeInitialize_RevertsWhenReFiCurrency0() public {
MockERC20 higherToken = new MockERC20("HIGH", "HIGH", 18);
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);
}
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();
+ }