DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Users May Underestimate Profit and Underpay Governance Fees and Lose their remaining Profit if possible

Summary

Users who are unaware of their full unrealized or potential profit may unintentionally avoid paying the correct governance fee. Since the protocol only charges fees on the amount exceeding their initial deposit, users who miscalculate their profit might withdraw in a way that minimizes fees without realizing it.
Or users who calculate based on their deposit (current price in which price fluctuate overtime when making withdrawal).

Vulnerability Details

  • Users might not know their potential profit due to Price Volatility
    in which the price of their collateral and position changes with market prices in which it regards to worth of their profit,

  • A user might assume they made a fixed profit, but their unrealized PnL could still be fluctuating at the moment of withdrawal

  • Perpetual protocols like GMX apply funding fees over time.

  • If a user doesn't account for funding fees, their expected profit might differ from their actual balance for users who withdraw based on if (curPositionKey != bytes32(0)) { (i.e., if position is open ).

  • Slippage and price impact

More resons why this is an issue is that

Once a user make a withdrawal transaction their deposit Id get deleted and deposit info which makes them not to be able to track any leftover profit or initial deposit that changes due to price

Impact

Stucked profit or remaining deposit in the system due to market changes
Users loss unrealized funds & profit
Protocol losses revenue since governance fee are never calculated for other potential profit of users.
Unfair system for users who miscalculate their profit

Tools Used

Manual Review

Recommendations

To prevent users from gaming the system, we should:

  1. Use _calculateUnrealizedProfit() to accurately track profit before allowing withdrawals.
    Example implementation

function calculateUnrealizedProfit(uint256 depositId) public view returns (uint256) {
DepsoitInfo memory deposit = depositInfo[depsoit];
if (deposit.shares == 0) {
return 0; // No active position
}
IVaultReader.PositionData memory positionData = vaultReader.getPositionInfo(curPositionKey, MarketPrices memory prices);
uint 256 currentValue = positionData.collateralAmount; // current position value
uint256 initialDeposit = deposit.amount; // Original deposit
if (currentValue > initialDeposit) {
return currentValue - initialDeposit; // Return profit
} else {
return 0; // No profit (or loss)
}

implementing it in the _transferToken function

function _transferToken(uint256 depositId, uint256 amount) internal {
uint256 fee;
// Calculate unrealized profit
uint256 profit = calculateUnrealizedProfit(depositId);
if (profit > 0) {
fee = (profit * governanceFee / BASIS_POINTS_DIVISOR;
if (fee > 0) {
collateralToken.safeTransfer(treasury, fee);
........
}

or implement a separate function function that returns over all profit of users before making withdrawals

Updates

Lead Judging Commences

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Users mistake, only impacting themselves.

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Users mistake, only impacting themselves.

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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