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

The `share` price can be manipulated through precision loss vulnerability to steal user funds.

Summary

Attackers can trigger precision loss issues by calling the deposit and withdraw functions in the PerpetualVault contract to manipulate share prices and steal assets deposited by other users.

Vulnerability Details

contracts/PerpetualVault.sol#L215-280

  • After the PerpetualVault contract is deployed, be the first to perform a deposit operation (amount = minDepositAmount = 1e8). At this time, shares will be minted in the _mint function according to _shares = depositInfo[depositId].amount * 1e8;, obtaining share = 1e16.

  • The attacker then sends a large amount of collateralToken to the contract (assume amount = 1e16), raising the share price to 1e16 share = 1e16 + 1e8 amount.

  • After the attacker raises the share price, perform another deposit operation (amount = 2 * 1e8). The share amount will be calculated according to _shares = amount * totalShares / totalAmountBefore;. The resulting share amount is 1.

# amount * totalShares / totalAmountBefore
>>> (2 * 1e8) * 1e8 / (1e16 + 1e8)
1.9999999800000001
>>> (2 * 1e8) * 1e8 // (1e16 + 1e8)
1.0

Contract state at this point: 1e16 + 1 share = 1e16 + 3e8 amount

  • The attacker then calls the withdraw function to retrieve the first funds (share = 1e16). According to amount = collateralToken.balanceOf(address(this)) * shares / totalShares;, the calculated amount = 1e16 + 3e8

# collateralToken.balanceOf(address(this)) * shares / totalShares
>>> (1e16 + 3e8) * 1e16 / (1e16 + 1)
1.00000003e+16
>>> (1e16 + 3e8) * 1e16 // (1e16 + 1)
1.00000003e+16

Contract state at this point: 1 share = 0 amount

  • The attacker sends another large amount of collateralToken to the contract (assume amount = 1e16), raising the share price to 1 share = 1e16 amount.

  • When victim users unknowingly make a deposit operation (amount = 1e15) after the attacker raises the share price, the share amount will be calculated according to _shares = amount * totalShares / totalAmountBefore;. The user receives 0 shares at this time. (As long as the user's deposit amount is less than 1e16, the obtained share will be 0).

# amount * totalShares / totalAmountBefore
>>> 1e15 * 1 / 1e16
0.1
>>> 1e15 * 1 // 1e16
0.0

Contract state at this point: 1 share = 1e16 + 1e15 amount

  • Finally, the attacker calls the withdraw function to retrieve the second funds (share = 1). According to amount = collateralToken.balanceOf(address(this)) * shares / totalShares;, the calculated amount = 1e16 + 1e15.

# collateralToken.balanceOf(address(this)) * shares / totalShares
>>> (1e16 + 1e15) * 1 / 1
1.1e+16
>>> (1e16 + 1e15) * 1 // 1
1.1e+16

Contract state at this point: 0 share = 0 amount, and the attacker has obtained the victim user's deposit of 1e15.

Impact

Attackers can manipulate the share price to prevent other users from obtaining share when depositing assets, thereby stealing user assets.
This attack scenario can be executed using a sandwich attack when discovering a user performing the first deposit operation, placing the user's deposit operation in the middle to profit.

Tools Used

Recommendations

It is recommended to refer to Compound or AAVE's approach, where the project team makes the first deposit when creating a new PerpetualVault.

Updates

Lead Judging Commences

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

invalid_collateral_balanceOf_donation

Prevent tokens from getting stuck. This is a simple donation to every shareholder, and therefore, every share has more value (profit), leading to fewer shares for future users. If this is not intended by the user, it is merely a mistake! For totalAmountBefore > amount * totalShares, Here is the worst-case scenario (with the minimum amount): first deposit 0.001000 (e4) USDC → 1e12 shares. Second deposit: 0.001 (e4) USDC * 1e12 / 0.001 (e4) USDC. So, the attacker would need to donate 1e16 tokens, meaning 1e10 USDC → 10 billion $. Even in the worst case, it's informational.

Support

FAQs

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