RebateFi Hook

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

Incorrect ReFi Buy/Sell Direction Detection, Leading to Full Fee Bypass or Wrong Fee Application.

Incorrect ReFi Buy/Sell Direction Detection, Leading to Full Fee Bypass or Wrong Fee Application.

Description

  • The _isReFiBuy() function incorrectly determines whether a swap represents a buy or sell of the ReFi token based solely on the token’s position in the pair (currency0 vs currency1). This flawed logic leads to misclassification of swap direction, causing the hook to apply the wrong fee type.


    As a result:

    • Selling ReFi be treated as a buy.

    • Buying ReFi be treated as a sell .

/// @notice Determines if a swap is buying or selling ReFi
/// @param key The pool key containing currency information
/// @param zeroForOne The swap direction
/// @return True if buying ReFi, false if selling
function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
@> bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
@> if (IsReFiCurrency0) {
@> return zeroForOne;
@> } else {
@> return !zeroForOne;
}
}

Risk

Likelihood:

  • It will happen in all cases


Impact:

  • There's a severe disruption of protocol functionality or availability.

Proof of Concept

<details>
<summary>POC</summary>
1. Add this tests to `RebateFiHookTest.t.sol` test file.
The first test proves that `buyFee` is applied instead of `sellFee` when user sells ReFi token while ReFi is currency0 .
```javascript
function test_isReFiBuy_WhenReFiIsCurrency0() public view {
// ------------------------------------------------------
// 1. Create a fake PoolKey where ReFi is currency0
// ------------------------------------------------------
PoolKey memory fakeKey;
fakeKey.currency0 = reFiCurrency; // Simulate: ReFi = currency0
fakeKey.currency1 = tokenCurrency;
// ------------------------------------------------------
// 2. SELL direction -> zeroForOne = true
// ------------------------------------------------------
bool zeroForOne = true; // selling ReFi
// ------------------------------------------------------
// 3. Reproduce the logic of _isReFiBuy
// ------------------------------------------------------
bool IsReFiCurrency0 = (
Currency.unwrap(fakeKey.currency0) == address(reFiToken)
);
bool isReFiBuy;
if (IsReFiCurrency0) {
isReFiBuy = zeroForOne;
} else {
isReFiBuy = !zeroForOne;
}
// ------------------------------------------------------
// 4. Determine what fee the hook WOULD apply
// ------------------------------------------------------
(uint24 buyFee, uint24 sellFee) = rebateHook.getFeeConfig();
uint24 appliedFee = isReFiBuy ? buyFee : sellFee;
// ------------------------------------------------------
// 5. Print the result
// ------------------------------------------------------
console.log("-------------------------------------------");
console.log("Simulated SELL ReFi (zeroForOne = true)");
console.log("ReFi considered as currency0");
console.log("Hook classification (isReFiBuy):", isReFiBuy);
console.log("Applied fee would be:", appliedFee);
console.log("-------------------------------------------");
// ------------------------------------------------------
// 6. Assert the bug: fee applied is wrong
// ------------------------------------------------------
assertEq(
appliedFee,
buyFee, // expected buggy behavior
"BUG: Hook should incorrectly apply BUY fee instead of SELL fee"
);
}
```
The second test proves that `sellFee` is applied instead of `buyFee` when user buys ReFi token while ReFi is currency1.
```javascript
function test_isReFiBuy_WhenReFiIsCurrency1() public view {
// ------------------------------------------------------
// 1. Fake PoolKey where ReFi is currency1
// ------------------------------------------------------
PoolKey memory fakeKey;
fakeKey.currency0 = tokenCurrency; // token is currency0
fakeKey.currency1 = reFiCurrency; // ReFi is currency1
// ------------------------------------------------------
// 2. BUY ReFi => zeroForOne = true
// selling tokenCurrency (currency0) and buying ReFi
// ------------------------------------------------------
bool zeroForOne = true;
// ------------------------------------------------------
// 3. Reproduce buggy _isReFiBuy logic
// ------------------------------------------------------
bool IsReFiCurrency0 = (
Currency.unwrap(fakeKey.currency0) == address(reFiToken)
);
bool isReFiBuy;
if (IsReFiCurrency0) {
isReFiBuy = zeroForOne;
} else {
isReFiBuy = !zeroForOne;
}
// ------------------------------------------------------
// 4. Determine which fee is applied
// ------------------------------------------------------
(uint24 buyFee, uint24 sellFee) = rebateHook.getFeeConfig();
uint24 appliedFee = isReFiBuy ? buyFee : sellFee;
// ------------------------------------------------------
// 5. Print the output
// ------------------------------------------------------
console.log("-------------------------------------------");
console.log("Simulated BUY ReFi (zeroForOne = true)");
console.log("ReFi is currency1");
console.log("Hook classification (isReFiBuy):", isReFiBuy);
console.log("Applied fee:", appliedFee);
console.log("Expected bug: sellFee should be applied instead of buyFee");
console.log("-------------------------------------------");
// ------------------------------------------------------
// 6. Assert the BUG
// BUY SHOULD APPLY buyFee = 0
// BUT BUG APPLIES sellFee = 3000
// ------------------------------------------------------
assertEq(
appliedFee,
sellFee,
"BUG: Hook wrongly applies SELL fee (3000) during a BUY operation"
);
}
```
</details>

Recommended Mitigation

1.Modify the `_isReFiBuy` function so that the verification is working correctly.
```diff
/// @notice Determines if a swap is buying or selling ReFi
/// @param key The pool key containing currency information
/// @param zeroForOne The swap direction
/// @return True if buying ReFi, false if selling
function _isReFiBuy(PoolKey calldata key, bool zeroForOne) internal view returns (bool) {
- bool IsReFiCurrency0 = Currency.unwrap(key.currency0) == ReFi;
- if (IsReFiCurrency0) {
- return zeroForOne;
- } else {
- return !zeroForOne;
- }
+ Currency input = zeroForOne ? key.currency0 : key.currency1;
+ Currency output = zeroForOne ? key.currency1 : key.currency0;
+
+ return Currency.unwrap(output) == ReFi;
}
```
Updates

Lead Judging Commences

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

Inverted buy/sell logic when ReFi is currency0, leading to incorrect fee application.

Support

FAQs

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

Give us feedback!