Patch Thursday — Retrospecting Unhealthy Order Allowance Vulnerability in Perpetual Protocol

ChainLight
ChainLight Blog & Research
8 min readNov 9, 2023

Summary

On October 3, 2022, ChainLight discovered a critical bug in the position value calculation mechanism in the AccountBalance contract of Perpetual Protocol. To put it simply, this vulnerability allowed the placement of orders for positions based on the index price (the price of underlying assets) within the protocol, rather than the current price of futures (mark price or market average price). This resulted in permitting position orders at prices outside the allowed range due to sudden fluctuations in asset prices within the protocol. The vulnerability allowed attackers to generate a substantial amount of bad debt for the protocol, and drain all the deposited USDC, which was about $32M.

However, the protocol’s perspective on the severity of the vulnerability was quite different from ours, leading to some disappointment in how the vulnerability report was handled. We presented a new attack method that bypasses existing defense mechanisms. However, the bounty offered was lower than the standard, and the severity of the vulnerability was downplayed. Perpetual protocol asserted the existence of a defense mechanism, even though it could be nullified by the vulnerability we found. We aim to share the specifics of the vulnerability and the challenges that security researchers may face during the vulnerability reporting process from this article.

Background

To understand this vulnerability, it is essential to grasp how the value of a position is calculated in futures trading. The method for calculating the value of a position in futures trading can vary depending on the type of contract and market conditions. However, it typically considers the index and mark price as crucial factors.

  • Index Price: This refers to the average price of the underlying asset across multiple exchanges. Since it is an average of prices from various exchanges, it is relatively stable compared to rapid price fluctuations on a specific exchange.
  • Mark Price: This represents the current price of the asset offered by the exchange where futures are traded. The value of a futures contract is primarily determined by the mark price and is commonly used for purposes such as liquidation and margin-related calculations.

On the other hand, the index price and market average price used in Perpetual Protocol differs from their common meanings.

  • Index Price: The current value of the spot asset. Perpetual Protocol handles sudden fluctuations in spot prices by utilizing Time-Weighted Average Price (TWAP). The protocol determines the feasibility of a position order based on the index price.
  • Mark Price: The price of the underlying asset in the most recently traded futures contract. Normally, mark price tends to follow the spot price, but can also experience sudden fluctuations depending on liquidity and the size of the traded contracts.

Vulnerability Details

In a typical futures exchange, the size of positions a user can order is limited based on the user’s initial margin. The exchange must ensure that (User's specified price) * (Number of contracts) / (Leverage) does not exceed the initial margin. Without this enforcement, users could place positions larger than their initial margin, resulting in an immediate bad debt when the order is executed. The vulnerability discovered in Perpetual Protocol also allowed such cases, causing a significant vulnerability.

Perpetual Protocol did not control the feasibility of an order in the manner described above. Instead, they calculated the value of positions and allowed orders based on the current price of the base token (index price) of positions. This becomes problematic when an attacker manipulates the spot token price.

Let us assume that an attacker manipulates the price of the spot token momentarily, creating a significant gap between the index and mark price. In this case, Perpetual Protocol would allow the attacker to order short maker positions at prices higher than what should be permitted, since the protocol calculates the position value based on the index price, which is artificially lower due to the attacker’s manipulation.

Subsequently, the attacker would manipulate the price again, raising the spot token price to execute the malicious order. This execution immediately leads to the bad debt within the protocol, as the attacker’s margin cannot cover the position in liquidation. The attacker can also earn unrealized profits as the spot token price returns to normal by the natural market movement.

If the attacker simultaneously submits the malicious short maker orders and opens long taker positions, this can hedge risks during the attack process due to the delta neutrality. Lastly, the vulnerability is deemed extremely severe because, due to an implementation issue in Perpetual Protocol, the owner of opened positions could not be identified or controlled before the position settlement. Consequently, the attacker can drain all the USDC deposited in the protocol, through the attack scenario that will be explained in the next section.

Attack Scenario

We tried to find the most inefficient pool for the quick price manipulation. Through crawling, we were able to identify that the most inefficient pool in Perpetual Protocol within the _MAX_PRICE_SPREAD_RATIO range. The target was the vMATIC-vUSD pool, which required approximately $280K for a single attack.

The attack requires a total of four accounts, and the attack process unfolds as follows. Let us assume that the initial index price of the base token is A.

1. Account 0 initiates a massive sell of spot token to drive the mark price of the vUSD pool down to a price 0.8A, which corresponds to 80% of the index price. This is due to the _MAX_PRICE_SPREAD_RATIO set in the isOverPriceSpread() function within the Exchange contract. Due to this restriction, the attacker can only cause a maximum price change of 20% from the index price obtained from the off-chain oracle. You can find the _MAX_PRICE_SPREAD_RATIO from the code below.

// perp-curie-contract/contracts/Exchange.sol:347
/// @inheritdoc IExchange
function isOverPriceSpread(address baseToken) external view override returns (bool) {
uint256 markPrice = getSqrtMarkTwapX96(baseToken, 0).formatSqrtPriceX96ToPriceX96().formatX96ToX10_18();
uint256 indexTwap =
IIndexPrice(baseToken).getIndexPrice(IClearingHouseConfig(_clearingHouseConfig).getTwapInterval());
uint256 spread = markPrice > indexTwap ? markPrice.sub(indexTwap) : indexTwap.sub(markPrice);
return spread > PerpMath.mulRatio(indexTwap, **_MAX_PRICE_SPREAD_RATIO**);
}

2. Account 1 opens a short maker position at the price 1.2A. Since the maximum range of order is 20% from the index price, 1.2A is the maximum price for the order within the attack. Since the initial margin follows the index price, Account 1 deposits the margin following the index price(0.8A) and the size of the position.

3. Account 2 places a long taker position on the price of 0.8A, and the attacker drives the price of vUSD up to a maximum of 1.2A through massive buy of spot token. Through this step, unrealized profit is generated from Account 2’s long position.

4. Account 3 opens a long taker position at price 1.2A as a counterparty for Account 1, executing the malicious short taker order on price 1.2A.

5. Account 2 closes its long position to realize profits.

In this process, the amount required by the attacker and the profit generated from the attack are as follows:

Attack Cost

Margin for positions of Account 1, 2, and 3

Attack Profit Calculation

1. Profit from Account 2’s settlement

= Position Size * {Index Price * (1+_MAX_PRICE_SPREAD_RATIO) - Index Price * (1-_MAX_PRICE_SPREAD_RATIO)} * Leverage

= Position Size * (Index Price * 2 * _MAX_PRICE_SPREAD_RATIO * Leverage)

2. Attack Cost

= Total Margin for Account 1,2,3’s Positions

= Position Size * {Index Price * (1-_MAX_PRICE_SPREAD_RATIO ) + Index Price * (1-_MAX_PRICE_SPREAD_RATIO) + Index Price * (1+_MAX_PRICE_SPREAD_RATIO)}

= Position Size * {Index Price * (3-_MAX_PRICE_SPREAD_RATIO)}

3. Attack Profit

= Profit from Account 2’s settlement — Attack cost

= Position Size * Index Price * {(2 * Leverage + 1) * _MAX_PRICE_SPREAD_RATIO - 3}

Assuming the same position size within the accounts, when applying the maximum leverage (10x), the maximum profit generated from the attack can be calculated as below:

Position Size * Index price * (21 * _MAX_PRICE_SPREAD_RATIO - 3)

Recommendation

As evident from the formula mentioned in the attacker’s profit calculation, the attacker’s profit is dependent on _MAX_PRICE_SPREAD_RATIO. Therefore, the protocol can prevent profit from the attack by adjusting _MAX_PRICE_SPREAD_RATIO to approximately 1/7, which is 14%.

However, this adjustment does not fix the root cause of the vulnerability, as this cannot solve the dependency of index price for position value estimation. Hence, the generation of bad debt is not prevented in this way. Here is our proposed solution:

  • Current mark price < Real-time mark price < Index price: In this case, the bad debt scenario can be met. Calculating position value using the current (not real-time) mark price can prevent attacks due to price manipulation.
  • Index price < Real-time mark price < Current mark price: Position value calculation should utilize the index price, or else short positions that have already reached malicious debt can be ordered. (However, in this case, there is a possibility of liquidation before reaching the ordered price.)
  • Current mark price < Index price < Real-time mark price: Calculate position value using the current mark price.
  • The other cases: We only considered the total USDC debt when a user attempts to open a long position, so there is no need to calculate position value in the other cases.

Impact

This vulnerability represented a critical flaw that could result in the theft of 10M USDC in just a single attack. Moreover, we proved that the attacker could steal the total amount of USDC deposited within the protocol (32M USDC) by repeating the attack multiple times. Additionally, since only 27 block timestamps are taken for a single attack, the protocol practically has no means of detecting or responding to the attack attempts. Optimism, where Perpetual protocol operates, has a block generation time of 2 seconds, thus the time for an attack would take less than 1 minute.

A Conflict in Bounty Mediation Process

Regrettably, during the process of negotiating the severity and bounty for the vulnerability with Perpetual Protocol, we encountered an unexpected conflict. Perpetual Protocol initially mentioned the vulnerability as an “exploit that circumvents the added parameter with existing defense mechanisms” when the vulnerability was reported. Despite the fact that the attack could potentially steal all the USDC deposited within the protocol, the protocol lowered the severity of the vulnerability from critical to medium, citing the existence of a defense mechanism that could be bypassed in our attack scenario. Additionally, they offered a bounty of only $5K, which was significantly lower than the maximum $250K bounty they posted for the critical vulnerabilities. With the active mediation process of Immunefi, we were able to restore the vulnerability’s severity to critical after a pretty long time, which took four months, and the bounty was increased to $10K during the mediation process.

It is crucial to remember that the vulnerabilities discovered through audits must be treated fairly and appropriately, for the healthy development of the Web3 ecosystem.

Proof of Concept

closeLongPosition()
currentTick[0]: -509
current sqrt mark price: 974874808816933968
current sqrt mark price: 970455847593735971
accounts[0] IM: 1745081012
accounts[0] MM: 6480816154
accounts[1] IM: -13613004255136
accounts[1] MM: -10813324686338
accounts[2] IM: 35657225962709
accounts[2] MM: 35657225962709
accounts[3] IM: -12713725138305
accounts[3] MM: -9413725138305
check the profit XD
Realized Profit $: 10007261
Total block/timestamp cost: 27

About ChainLight

Patch Thursday is ChainLight’s weekly report introducing and analyzing vulnerabilities discovered and reported by our award-winning security researchers. With the mission to assist security researchers and developers in collectively learning, growing, and contributing to making Web3 a safer place, we release our report every week, free of charge.

To receive the latest research and reports conducted by award-winning experts:

👉 Subscribe to our newsletter

👉 Follow @ChainLight_io

Established in 2016, ChainLight’s award-winning experts provide tailored security solutions to fortify your smart contract and help you thrive on the blockchain.

--

--

ChainLight
ChainLight Blog & Research

Established in 2016, ChainLight's award-winning experts provide tailored security solutions to fortify your smart contract and help you thrive on the blockchain