RebateFi Hook

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

Due to a typo in the comparison in the `ReFiSwapRebateHook::_beforeInitialize` function, many of the tokens fail to pair with the `ReFi` token, making the program non-functional.

Root + Impact

Description

  • In the ReFiSwapRebateHook::_beforeInitialize function, there is an if statement which tries to make sure the ReFi token is one of the tokens forming the LP token pair.

  • However, due to the apparent typo, it only compares the second token (Currency.unwrap(key.currency1)) with ReFi token, twice.

  • Therefore, it passes through, if and only if ReFi token is the second token in the pair. It reverts even if the first token (Currency.unwrap(key.currency0) is ReFi.

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: High

  • It can happen literally for half of the times the _beforeInitialize function gets executed. It reverts depending on the position of the ReFi token in the pair (whether it is the first token or the second one). The position of the tokens in the pair are decided based on the ascending order of their addresses.


Impact: High

  • This is a problem in the implementation of the business logic. While it is supposed to revert only for LP token pairs where none of them is ReFi token, it reverts even if the first token in the pair is ReFi. It severely disrupts the protocol.

Proof of Concept

Please copy/paste the following code (contract including the test function) to the end of the test file.

Then, run the test function using the command below.

forge test --mt test_theRefiTokenCannotBeTheFirstInThePair -vvvv

You will see the revert with the custom error (ReFiNotInPool) in the output.

// Arrange
contract TestReFiSwapRebateHook2 is Test, Deployers, ERC1155TokenReceiver {
MockERC20 public reFiToken;
ReFiSwapRebateHook public rebateHook;
Currency ethCurrency;
// Currency tokenCurrency;
Currency reFiCurrency;
uint160 constant SQRT_PRICE_1_1_s = 79228162514264337593543950336;
function setUp() public {
// Deploy the Uniswap V4 PoolManager
deployFreshManagerAndRouters();
// Deploy the ReFi token
reFiToken = new MockERC20("ReFi Token", "ReFi", 18);
reFiCurrency = Currency.wrap(address(reFiToken));
// To make sure ethCurrency has an address bigger than reFiToken's address (to be the second token in the pair based on ascending order of addresses).
ethCurrency = Currency.wrap(address(uint160(address(reFiToken)) + 1));
// Get creation code for hook
bytes memory creationCode = type(ReFiSwapRebateHook).creationCode;
bytes memory constructorArgs = abi.encode(manager, address(reFiToken));
// Find a salt that produces a valid hook address
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
);
// Deploy the hook with the mined salt
rebateHook = new ReFiSwapRebateHook{salt: salt}(
manager,
address(reFiToken)
);
require(address(rebateHook) == hookAddress, "Hook address mismatch");
}
function test_theRefiTokenCannotBeTheFirstInThePair() public {
// Act & Assert
vm.expectRevert();
(key, ) = initPool(
// The ReFi token is the first token in the pair here
reFiCurrency,
ethCurrency,
rebateHook,
LPFeeLibrary.DYNAMIC_FEE_FLAG,
SQRT_PRICE_1_1_s
);
}
}

Recommended Mitigation

The solution is quite easy. Just make the following adjustments.

function _beforeInitialize( address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
if (
- Currency.unwrap(key.currency1) != ReFi &&
+ 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!