Root + Impact
Description
-
With the ReFi token as Currency1 of the Liquidity Pool, ReFi buying swaps should be incentivized with low or no LPFees involved for the transaction and ReFi selling swaps should be detered with 0.3% LPFees involved for the swap
-
Most critically, in the beforeSwap hook function, the ReFi buy LPFees logic is wrapped in an if statement that depends on the wrong boolean value from the isReFiBuy function.
Inside the isReFiBuy function, the return value is true only when ReFi is currency0, which will always return a false when ReFi isnt currency0, and result into application of the correct zeroForOne= false logic to a zeroForOne=true swap, according to the pool currency setup, charging 0.3% on every Buy Swap of ReFi token
function beforeSwap(...) internal override returns(...){
@> bool isReFiBuy = _isReFiBuy(key, params.zeroForOne);
...
@> if (isReFiBuy) {
fee = buyFee;
emit ReFiBought(sender, swapAmount);
} else {
fee = sellFee;
uint256 feeAmount = (swapAmount * sellFee) / 100000;
emit ReFiSold(sender, swapAmount, feeAmount);
}
...
}
function isReFiBuy(...) internal view returns(bool){
...
@> bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
...
@> if (IsReFiCurrency0) {
return zeroForOne;
} else {
return !zeroForOne;
}
}
Risk
Likelihood:
Impact:
-
User will be charged 0.3% LPFees for Buying ReFi Tokens which is contradictory to the Hook's purpose
-
User will be charged close to no fees for selling ReFi Token, incentive to liquiadate ReFi token assets
Proof of Concept
_beforeSwap(
0x3.....,
key,
params,
calldata
) internal override returns (bytes4, BeforeSwapDelta, uint24) {
bool isReFiBuy = _isReFiBuy(key, params.zeroForOne);
...
if (isReFiBuy) {
...
}
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
);
}
Recommended Mitigation
- remove this code
//entire function is futile, as params.zeroForOne in beforeSwap will suffice
function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
// @> Following up that the requirement for this pool to be initlalizedis if ReFi is currency1
// this if statement is problematic, since the true variant of the bool isReFiCurrency0, implies
// that ReFi is actually being sold, not bought. The entire purpose of the Hook is reversed
// A deterent to Buy Refi and incentive to sell is instead established
if (IsReFiCurrency0) {
return zeroForOne;
} else {
return !zeroForOne;
}
}
function beforeSwap(...)...{
...
@> bool isReFiBuy = _isReFiBuy(key, params.zeroForOne);
...
}
+ add this code
function beforeSwap(...)...{
...
@> bool isReFiBuy = params.zeroForOne;
...
}