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

Incorrect Fee Calculation in `PerpetualVault::_transferToken` Function

Summary

The fee calculation in _transferToken does not account for rounding errors, which could lead to incorrect fee distribution or loss of funds.

Vulnerability Details

The fee is calculated as (amount - depositInfo[depositId].amount) * governanceFee / BASIS_POINTS_DIVISOR.

If (amount - depositInfo[depositId].amount) is small, the fee could round down to zero, leading to no fee being collected.

The protocol allows minDepositAmount = 1000 (likely in USD with 30 decimals), enabling tiny positions where rounding issues are significant.

  • Example: For sizeDeltaUsd = 1e15 (0.001 USD), even a 5% fee would compute to 0.00005 USD, which rounds to zero

  • The rounding error directly contradicts the protocol’s goal of ensuring fees are "proportional to each depositor's share."

Conceptual Scenario: Exploiting Incorrect Fee Calculation in _transferToken

Participants:

Alice: A legitimate user who deposits funds into the PerpetualVault contract.

Bob: A malicious attacker who exploits the incorrect fee calculation vulnerability.

PerpetualVault: The vulnerable smart contract that allows users to deposit and withdraw funds.

Step 1: Alice Deposits Funds

Alice deposits 100 tokens into the PerpetualVault contract. The contract records her deposit and assigns her a depositId.

Step 2: Bob Prepares for the Attack

Bob notices that the fee calculation in the _transferToken function is vulnerable to rounding errors. Specifically, the fee is calculated as:

fee = (amount - depositInfo[depositId].amount) * governanceFee / BASIS_POINTS_DIVISOR;

If (amount - depositInfo[depositId].amount) is small, the fee could round down to zero, leading to no fee being collected.
Bob decides to exploit this by making small withdrawals that trigger the rounding error.

Step 3: Bob Deposits Funds

Bob deposits 1 token into the PerpetualVault contract. This gives him a valid depositId and makes him eligible to withdraw funds.

Step 4: Bob Initiates the Attack

Bob calls the withdraw function on the PerpetualVault contract, specifying his depositId and setting the recipient address to his own wallet.

The withdraw function calculates the amount Bob is eligible to withdraw. Since Bob deposited 1 token, he is entitled to withdraw 1 token (plus any profit, if applicable).

Step 5: Exploiting the Fee Calculation

When the _transferToken function is called, it calculates the fee as:

fee = (amount - depositInfo[depositId].amount) * governanceFee / BASIS_POINTS_DIVISOR;

If amount (the withdrawal amount) is only slightly larger than depositInfo[depositId].amount (the deposited amount), the result of (amount - depositInfo[depositId].amount) could be a very small number (e.g., 1 wei).
When this small number is multiplied by governanceFee (e.g., 500 for 5%) and divided by BASIS_POINTS_DIVISOR (10,000), the result could round down to zero due to Solidity's integer division.
As a result, no fee is collected, and Bob receives the full withdrawal amount without paying any fees.

Step 6: Repeating the Attack

Bob repeats this process multiple times, making small withdrawals that trigger the rounding error.

Each time, he avoids paying fees, effectively draining the protocol's revenue.

Step 7: Impact on the Protocol

The protocol loses revenue because fees are not collected on small withdrawals.

Over time, this could lead to significant financial losses for the protocol, especially if multiple attackers exploit this vulnerability.

Impact

Loss of protocol revenue or incorrect fee distribution.

Tools Used

Manual Code Review

Recommendations

Use a more precise fee calculation method, such as multiplying before dividing to minimize rounding errors.
Ensure the fee is always at least 1 wei if the amount exceeds the deposit amount.

uint256 profit = amount - depositInfo[depositId].amount;
uint256 fee = (profit * governanceFee) / BASIS_POINTS_DIVISOR;
if (fee == 0 && profit > 0) {
fee = 1; // Ensure at least 1 wei is collected as fee
}
Updates

Lead Judging Commences

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

Informational or Gas

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.

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

Support

FAQs

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