Root + Impact
Description
Normally, a Uniswap V4 hook validates that swaps and liquidity modifications occur only for pools that contain the designated tokens. This ensures that fee logic or custom behavior is applied only to intended pools.
In ReFiSwapRebateHook, the _beforeSwap function does not validate that the PoolKey corresponds to the designated ReFi token. This allows the hook to be attached to, or invoked by, arbitrary pools containing different tokens, potentially applying unintended fee overrides.
function _beforeSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
bytes calldata
) internal override returns (bytes4, BeforeSwapDelta, uint24) {
bool isReFiBuy = _isReFiBuy(key, params.zeroForOne);
uint256 swapAmount = params.amountSpecified < 0
? uint256(-params.amountSpecified)
: uint256(params.amountSpecified);
uint24 fee;
if (isReFiBuy) {
fee = buyFee;
emit ReFiBought(sender, swapAmount);
} else {
fee = sellFee;
uint256 feeAmount = (swapAmount * sellFee) / 100000;
emit ReFiSold(sender, swapAmount, feeAmount);
}
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
fee | LPFeeLibrary.OVERRIDE_FEE_FLAG
);
}
Risk
Likelihood:
The hook will apply its fee logic to any pool calling _beforeSwap, including pools that do not contain the ReFi token, because there is no explicit check on key against the designated ReFi token.
Users or contracts attaching this hook to other pools will inadvertently trigger fee overrides without being restricted.
Impact:
Incorrect fees may be applied to unrelated pools, allowing a malicious user to manipulate swaps for profit or disrupt other liquidity pools.
Incentive mechanisms (buy/sell rebates) could be misapplied, undermining economic models and potentially causing token imbalance or unintended revenue generation.
Proof of Concept
Written explanation: The following PoC demonstrates that a malicious pool containing a different ERC20 token can call the hook and trigger ReFi sell fees, even though the pool does not contain the ReFi token. This can be tested in a Forge unit test environment.
function test_IncorrectPoolTokenValidation() public {
MockERC20 fakeToken = new MockERC20("Fake", "FAKE", 18);
Currency fakeCurrency = Currency.wrap(address(fakeToken));
fakeToken.mint(user1, 1 ether);
PoolKey memory fakeKey;
(fakeKey, ) = initPool(
ethCurrency,
fakeCurrency,
rebateHook,
LPFeeLibrary.DYNAMIC_FEE_FLAG,
79228162514264337593543950336
);
vm.deal(user1, 1 ether);
vm.startPrank(user1);
SwapParams memory params = SwapParams({
zeroForOne: false,
amountSpecified: -int256(0.01 ether),
sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
});
PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({
takeClaims: false,
settleUsingBurn: false
});
swapRouter.swap(fakeKey, params, testSettings, ZERO_BYTES);
vm.stopPrank();
uint24 currentSellFee;
(, currentSellFee) = rebateHook.getFeeConfig();
assertEq(currentSellFee, 3000, "Sell fee incorrectly applied to unrelated pool");
}
Recommended Mitigation
Explicitly check that the pool’s token matches the ReFi token before applying fee logic.
- bool isReFiBuy = _isReFiBuy(key, params.zeroForOne);
+ require(key.token1 == Currency.wrap(ReFi()) || key.token0 == Currency.wrap(ReFi()), "Hook only valid for ReFi pool");
+ bool isReFiBuy = _isReFiBuy(key, params.zeroForOne);