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

Incorrect Token Transfer in Liquidation and Withdrawals for Leveraged Positions

Summary

In PerpetualVault.sol, when a user withdraws funds or closes a leveraged position, the contract incorrectly transfers collateralToken instead of the actual borrowed token.

This issue occurs when self-lending pods are used, where the borrowed token differs from the collateral token. Since collateralToken.safeTransfer() is called in _transferToken(), it assumes that the collateral token is the correct asset, which is not always true for leveraged positions.

If a user attempts to withdraw or close a leveraged position, the contract may:

  • Fail the transaction, preventing withdrawals.

  • Send the wrong token, resulting in fund loss or incorrect settlements.

Vulnerability Details

When a user withdraws funds or closes a leveraged position in PerpetualVault.sol, the contract incorrectly transfers collateralToken instead of the actual borrowed token.

Root Cause

  1. The contract always assumes collateralToken is the correct asset, even when a position was opened with leverage.

  2. If self-lending pods were used, the actual borrowed token should be returned instead.

  3. The incorrect assumption leads to failed withdrawals or incorrect token settlements.

** Affected Code:** _transferToken() (Incorrect Token Transfer)

try collateralToken.transfer(depositInfo[depositId].recipient, amount - fee) {}

This assumes collateralToken is correct, but for leveraged positions, the borrowed token should be used instead.

Affected Code: _withdraw() (Fails for Leveraged Positions)

try collateralToken.transfer(depositInfo[depositId].recipient, amount - fee) {}

If the position was opened with a self-lending pod, the wrong token is used for withdrawal calculations.

Scenario 1: User Cannot Withdraw from a Leveraged Position

Context:

  • Trader Alice wants to go long on ETH/USD with 3x leverage.

  • She deposits 1,000 USDC into the PerpetualVault.

  • The vault borrows 2,000 USDC and swaps it to ETH, opening a 3,000 USDC long position on GMX.

  • Alice's position is now collateralized in ETH, but she originally deposited USDC.

The Issue:

  • After some time, Alice wants to withdraw her funds.

  • The vault only knows about the original deposit token (USDC) and attempts to transfer USDC back to Alice.

  • Since Alice’s collateral is in ETH, there is no USDC in the vault to return.

  • The withdrawal fails, preventing Alice from exiting her position.

** Impact:**

  • Alice is locked in the vault and cannot reclaim her funds unless the vault swaps ETH back into USDC manually.

  • If ETH drops in value while she’s unable to exit, Alice could be liquidated and lose everything.

Scenario 2: Wrong Token Sent to the User

Context:

  • Trader Bob opens a 2x short position on BTC/USD.

  • He deposits 5,000 USDC into the PerpetualVault.

  • The vault borrows BTC using a self-lending protocol and opens a 10,000 USDC BTC short on GMX.

  • Since Bob's collateral is BTC, he expects to receive BTC when withdrawing.

The Issue:

  • Bob decides to close his position and withdraw funds.

  • The vault mistakenly tries to send USDC instead of BTC.

  • Since the vault doesn't have USDC (it was used to borrow BTC), Bob's withdrawal fails.

  • Alternatively, the vault might wrongly send other users' USDC, causing a fund imbalance.

Impact:

  • If the vault incorrectly sends USDC instead of BTC, Bob receives an incorrect amount.

  • Bob loses money because he expected BTC at a certain price, but he was given an incorrect amount of USDC instead.

Impact

This issue directly affects user withdrawals, liquidations, and overall vault solvency.

Impact Area Effect Severity
Users Cannot Withdraw Funds Locked positions prevent users from exiting 🔴 High
Incorrect Token Sent to Users Unexpected losses due to mispricing 🔴 High
Malicious Exploits in Liquidations Attackers drain vault funds 🔴 High
Vault Insolvency The vault ends up owing more than it holds 🔴 High

Tools Used

Manual Review

Recommendations

  1. Introduce _getBorrowTokenForVault() to correctly identify whether to return collateral or borrowed assets.

  2. Use _getBorrowTokenForVault() in _transferToken() and _withdraw() to ensure users receive the right token.

  3. Modify _createIncreasePosition() to store the borrowed token when a leveraged position is opened.

** Fix for** _transferToken()

try IERC20(_getBorrowTokenForVault()).transfer(depositInfo[depositId].recipient, amount - fee) {}
catch {
IERC20(_getBorrowTokenForVault()).transfer(treasury, amount - fee);
emit TokenTranferFailed(depositInfo[depositId].recipient, amount - fee);
}

Implement _getBorrowTokenForVault()

function _getBorrowTokenForVault() internal view returns (address) {
if (selfLendingBorrowToken != address(0)) {
return selfLendingBorrowToken; // If a self-lending pod exists, use this as borrow token
}
return address(collateralToken); // Otherwise, return normal collateral
}
Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

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.

Give us feedback!