RebateFi Hook

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

Incorrect Fee Amount in Event Emission

Root + Impact

Description

  • The protocol implements dynamic swap fees where fees are represented in hundredths of a bip (i.e., units of 0.0001%), following Uniswap V4's fee convention [1, 2]. A fee value of 3000 should represent 0.3% (3000 × 0.0001% = 0.3%).

  • The _beforeSwap function returns the correct fee value to the pool manager for charging, but incorrectly calculates the fee amount for event emission by dividing by 100,000 instead of 1,000,000. This results in the ReFiSold event reporting a fee amount that is 10 times higher than what users actually pay.

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:

  • All ReFi sell transactions emit the incorrect event data.

Impact:

  • Off-chain systems and indexers relying on the ReFiSold event receive incorrect fee data (10x inflated)

  • Financial reporting and accounting based on events will be inaccurate

  • Users monitoring events may incorrectly believe they are being overcharged, damaging protocol reputation

Proof of Concept

First, correct the inverted logic in _I

function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
if (IsReFiCurrency0) {
- return zeroForOne;
+ return !zeroForOne;
} else {
- return !zeroForOne;
+ return zeroForOne;
}
}

Then, add this test to test/RebateFiHookTest.t.sol:

function test_FeeEventReportsInflatedAmount() public {
vm.startPrank(user1);
reFiToken.approve(address(swapRouter), type(uint256).max);
uint256 amount = 100 ether;
// Expecting correct amount: 100 * 3000 / 1_000_000 = 0.3 ETH
// But hook emits: 100 * 3000 / 100000 = 3 ETH (10x too large)
vm.expectEmit(true, true, true, true);
emit ReFiSwapRebateHook.ReFiSold(address(swapRouter), amount, 0.0003 ether); // what the contract *currently* emits
SwapParams memory params = SwapParams({
zeroForOne: false, // ReFi -> ETH
amountSpecified: -int256(amount),
sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
swapRouter.swap(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
}

Recommended Mitigation

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;
+ uint256 feeAmount = (swapAmount * sellFee) / 1_000_000;
emit ReFiSold(sender, swapAmount, feeAmount);
}
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
fee | LPFeeLibrary.OVERRIDE_FEE_FLAG
);
}
Updates

Lead Judging Commences

chaossr Lead Judge 12 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Incorrect denominator (100000 instead of likely 1000000 or 10000) in fee calculation for ReFiSold event.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!