RebateFi Hook

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

Pool Creation Denial of Service for ReFi as Currency0 (Logic Error + DoS)

Description:
The _beforeInitialize function contains a logic error in its check:

if (Currency.unwrap(key.currency1) != ReFi &&
Currency.unwrap(key.currency1) != ReFi) { // Checks currency1 twice!
revert ReFiNotInPool();
}

It checks currency1 twice and never checks currency0.
If a pool is created where ReFi is currency0 (e.g. ReFi/USDC, if ReFi < USDC), key.currency1 will be USDC (not ReFi).
The condition USDC != ReFi && USDC != ReFi is true.
The function reverts ReFiNotInPool.

Impact:
It is impossible to create any pool where ReFi is the sorted currency0. This restricts the tokens ReFi can be paired with (only tokens with addresses lower than ReFi can be paired if ReFi is to be currency1).

Proof of Concept:

function test_Bug_PoolCreationDoS() public {
// Create tokens such that ReFi < OtherToken
// ReFi becomes currency0.
// Init pool reverts.
vm.expectRevert();
initPool(reFiCurrency, otherCurrency, ...);
}

Test file

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {ReFiSwapRebateHook} from "../src/RebateFiHook.sol";
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
import {PoolSwapTest} from "v4-core/test/PoolSwapTest.sol";
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
import {PoolManager} from "v4-core/PoolManager.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {SwapParams, ModifyLiquidityParams} from "v4-core/types/PoolOperation.sol";
import {Currency, CurrencyLibrary} from "v4-core/types/Currency.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
import {TickMath} from "v4-core/libraries/TickMath.sol";
import {HookMiner} from "v4-periphery/src/utils/HookMiner.sol";
import {LPFeeLibrary} from "v4-core/libraries/LPFeeLibrary.sol";
contract AuditReproduction is Test, Deployers {
using CurrencyLibrary for Currency;
MockERC20 token;
MockERC20 reFiToken;
ReFiSwapRebateHook public rebateHook;
Currency ethCurrency = Currency.wrap(address(0));
Currency reFiCurrency;
Currency tokenCurrency;
address user1 = address(0x1);
function setUp() public {
deployFreshManagerAndRouters();
reFiToken = new MockERC20("ReFi Token", "ReFi", 18);
reFiCurrency = Currency.wrap(address(reFiToken));
token = new MockERC20("TOKEN", "TKN", 18);
tokenCurrency = Currency.wrap(address(token));
reFiToken.mint(address(this), 1000 ether);
reFiToken.mint(user1, 1000 ether);
token.mint(address(this), 1000 ether);
bytes memory creationCode = type(ReFiSwapRebateHook).creationCode;
bytes memory constructorArgs = abi.encode(manager, address(reFiToken));
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
);
rebateHook = new ReFiSwapRebateHook{salt: salt}(manager, address(reFiToken));
reFiToken.approve(address(rebateHook), type(uint256).max);
reFiToken.approve(address(swapRouter), type(uint256).max);
reFiToken.approve(address(modifyLiquidityRouter), type(uint256).max);
}
function test_Bug_PoolCreationDoS() public {
MockERC20 otherToken;
while (address(otherToken) <= address(reFiToken)) {
otherToken = new MockERC20("Other", "OTH", 18);
}
Currency currency0 = reFiCurrency;
Currency currency1 = Currency.wrap(address(otherToken));
if (Currency.unwrap(currency0) > Currency.unwrap(currency1)) {
(currency0, currency1) = (currency1, currency0);
}
require(Currency.unwrap(currency0) == address(reFiToken), "ReFi should be currency0");
vm.expectRevert();
initPool(
currency0,
currency1,
rebateHook,
LPFeeLibrary.DYNAMIC_FEE_FLAG,
SQRT_PRICE_1_1
);
}
}

Recommended Mitigation:
Check both currencies.

function _beforeInitialize(...) {
- 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 12 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!