Root + Impact
Description
-
The ReFiSold event should correctly log the amount of ReFi tokens being sold and the actual fee charged by the pool
-
2 problems. First SwapEvent represent the wrong token and secondly the fee calculation is incorrect
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, re
fee | LPFeeLibrary.OVERRIDE_FEE_FLAG
);
}
Risk
Likelihood:
Impact:
Proof of Concept
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {Currency} from "v4-core/types/Currency.sol";
import {SwapParams} from "v4-core/types/PoolOperation.sol";
import {ReFiSwapRebateHook} from "../src/RebateFiHook.sol";
contract EventMisreportingTest is Test {
ReFiSwapRebateHook hook;
PoolKey poolKey;
address refiToken = address(0x123);
address usdcToken = address(0x456);
event ReFiSold(address indexed seller, uint256 amount, uint256 fee);
function setUp() public {
IPoolManager poolManager = IPoolManager(address(0x789));
hook = new ReFiSwapRebateHook(poolManager, refiToken);
poolKey = PoolKey({
currency0: Currency.wrap(usdcToken),
currency1: Currency.wrap(refiToken),
fee: 0x800000,
tickSpacing: 60,
hooks: hook
});
}
function testExactOutputSwapLogsWrongToken() public {
SwapParams memory params = SwapParams({
zeroForOne: false,
amountSpecified: 1000e6,
sqrtPriceLimitX96: 0
});
vm.expectEmit(true, false, false, false);
emit ReFiSold(address(this), 1000e6, 0);
hook.beforeSwap(address(this), poolKey, params, "");
}
function testFeeCalculation10xOverreported() public {
SwapParams memory params = SwapParams({
zeroForOne: false,
amountSpecified: -10_000e18,
sqrtPriceLimitX96: 0
});
(, , uint24 actualFee) = hook.beforeSwap(address(this), poolKey, params, "");
uint256 actualFeeAmount = (10_000e18 * 3000) / 1_000_000;
assertEq(actualFeeAmount, 30e18);
uint256 reportedFeeAmount = (10_000e18 * 3000) / 100_000;
assertEq(reportedFeeAmount, 300e18);
assertEq(reportedFeeAmount, actualFeeAmount * 10);
}
}
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;
+ // For events, use absolute value - actual amount determined by pool
+ uint256 amount = params.amountSpecified < 0
+ ? uint256(-params.amountSpecified)
+ : uint256(params.amountSpecified);
+ emit ReFiBought(sender, amount);
- emit ReFiBought(sender, swapAmount);
} else {
fee = sellFee;
+ // Calculate fee using Uniswap's divisor (1,000,000)
+ uint256 amount = params.amountSpecified < 0
+ ? uint256(-params.amountSpecified)
+ : uint256(params.amountSpecified);
+ uint256 feeAmount = (amount * sellFee) / 1_000_000;
- uint256 feeAmount = (swapAmount * sellFee) / 100000;
+ emit ReFiSold(sender, amount, feeAmount);
- emit ReFiSold(sender, swapAmount, feeAmount);
}
}