The fee calculation in _transferToken
does not account for rounding errors, which could lead to incorrect fee distribution or loss of funds.
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."
_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:
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:
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.
Loss of protocol revenue or incorrect fee distribution.
Manual Code Review
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.
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.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.