Feb 16, 2022

The $200m Bluff: Cheating Oracles on Solana

How we fooled oracles to beat the house. An exploration into liquidity tokens and oracle price manipulation.

Heading image of The $200m Bluff: Cheating Oracles on Solana

We discovered a vulnerability in Switchboard's liquidity pool token price feeds which could hypothetically allow an attacker to manipulate the price of liquidity pool tokens and possibly steal lending protocol funds. This was reported to all affected lending protocols.

At the time of reporting, the following protocols were identified at risk:

ProtocolValue
Apricot$200m
Larix$150m
Port$100m
Tulip$120m
Parrot.fi$2m

We would also like to thank nojob for his contributions with the fair pricing of liquidity pool tokens.

Background

We'll first cover some relevant background concepts.

AMMs

AMMs or automated market makers on Solana are an onchain program allowing users to swap between two tokens, for example, SOL and USDC. This process is automated, and the program helps "make the market" by always allowing token swaps, hence the name automated market maker.

AMMs have their own token accounts which they use in the token swap. The price, or amount of one token you can get for another, is solely defined by the token account amounts.

This relationship can be modeled as a curve, where swapping tokens moves the price along the curve. For example, see this graph from the Stable Swap whitepaper.

Swap graphs

In this post, we'll by using constant product AMMs as our examples, but this vulnerability would apply to any AMM type.

Liquidity Pool Tokens

In order to function, AMMs need to maintain pools of the two respective assets. Users can deposit these assets into the AMM, a process known as providing liquidity, in exchange for rewards. In exchange for providing liquidity to an AMM, users are given liquidity pool tokens. These liquidity pool tokens can then later be redeemed for the original deposited assets.

An important thing to note is that the price of a liquidity pool token is not constant.

Consider the following scenario. Our SOL-USDC constant product AMM has 1500 USDC and 10 SOL deposited, with 10 LP tokens minted. This means each LP token can be redeemed for 150 USDC and 1 SOL.

Assuming the price of SOL is $150, the oracle would then calculate that each LP token can be redeemed for

For a constant product AMM, a normal swap of 1 SOL for USDC would then return approximately 150 USDC.

However, if someone swaps in 1500 USDC worth of SOL, the AMM will have 3000 USDC and 5 SOL, because they drove up the price of SOL in this pool.

It's easy to confirm that this maintains the constant product invariant.

Let's calculate the value of a single LP token now. For simplicity, let's assume that the AMM doesn't take any fees.

Similar to the math previously, each LP token can now be redeemed for

The value of the LP token is now

Anyone who owns an LP token for this pool has effectively gained $75! (assuming the value of USDC and SOL haven't changed)

Constant product doesn't mean constant value

As an aside, liquidity pool tokens are also an interesting example of how DeFi focuses on maximizing yields. Many lending protocols allow users to deposit LP tokens as collateral, and gain liquidity mining rewards along with interest on top of the normal liquidity pool yields. However, the extra complexity added by using liquidity pool tokens can easily lead to security problems.

Oracles

On Solana many platforms (leveraged farming, lending, perps, etc) rely on oracles to determine the true value of an asset. When oracles misreport prices this can result in stolen funds or insolvency of their respective protocols.

Most oracle values are compiled from a variety of onchain and offchain sources. However, liquidity pool tokens by nature have a single point of failure: calculating how many backing tokens an LP token can be redeemed for, which is based off the token balance of the relevant liquidity pool.

One such oracle is Switchboard, which is used by many popular lending protocols. Internally, switchboard sends a GET request to the respective swap protocol's API every minute, and directly pushes that price onchain.

For example, the switchboard task configuration for the price of stSOL on Raydium:

{
  "tasks": [
    {
      "httpTask": {
        "url": "https://api.raydium.io/pairs"
      }
    },
    {
      "jsonParseTask": {
        "path": "$[?(@.name == 'stSOL-USDC')].price"
      }
    }
  ]
}

The Raydium API calculates liquidity pool token values by pulling token balances from the respective AMM onchain. In other words, Switchboard's oracle was directly reflecting the onchain state.

Exploitation

Putting this all together, we start to see something suspicious. Pricing of liquidity pool tokens rely directly on onchain state, which can be easily manipulated.

Recall previously that by swapping a large amount into a given AMM, we are able to change the global price for that AMM's liquidity pool token.

This transaction can be executed right before an oracle update. Alternatively, an attacker could lock the AMM by spamming transactions to effectively deny usage of the AMM to prevent arbitrage until the oracle updates.

With our previous example, we manipulated the price of a liquidity pool token from $300 to $375. As a more extreme example, consider the transaction of 998500 USDC. After such a swap, the AMM will have 1 million USDC and 0.015 SOL. The oracle would report a value of over $100,000 per token.

Now let's explore how this manipulation can be used to steal funds.

Lending

Lending is the simplest to exploit. By holding and manipulating the liquidity pool token price, we are able to directly affect the value of our underlying collateral. We are then able to overborrow against this collateral, stealing money from the lending protocol.

More specifically, consider the following process.

  1. Manipulate price by swapping a large amount in the AMM
  2. Oracle updates, liquidity pool token is now overvalued
  3. Sell/buy back all the tokens to move AMM price to a sane value
  4. Mint liquidity pool tokens -> Deposit as collateral -> Borrow against that overvalued collateral
  5. Oracle gets updated
  6. Get liquidated by the lending protocol, but keep the borrowed assets

As a more concrete example, consider if an attacker spoofed the LP token price to $100,000,000, which was then uploaded to the oracle feed. After the oracle update, the attacker could move the LP token price back to a sane value such as $100. They would then be able to mint LP tokens for $100 each.

Because the oracle prices are cached, the lending protocol mistakenly believes each LP token is worth $100,000,000. If the attacker deposited 1 LP token, they could then borrow against $100,000,000 of collateral, even though the real value of their deposits is only $100. For example, if the LTV of the lending protocol is 75%, they would be able to borrow $75,000,000 worth of tokens.

After the price updates they'll get liquidated, but keep the borrowed $75,000,000 of tokens. This gives a net profit of $74,999,900 minus any costs of the manipulation (for example swap fees).

Leveraged Farming

Since you can’t directly deposit liquidity pool tokens for leveraged farming, you must open a leveraged position, manipulate price, up your leverage (which increases liquidity that can be arbitraged because it's at a bad price), sell off outside the farm and set price back to normal.

The steps are as follows.

  1. Manipulate price by swapping a large amount in the AMM
  2. Oracle updates, liquidity pool token is now overvalued
  3. Leverage higher valued collateral liquidity pool tokens to mint more liquidity pool tokens
  4. This increases the liquidity in the pool - perform "arbitrage" on the unbalanced liquidity pool
  5. Oracle gets updated
  6. Get liquidated, but keep profits from the "arbitrage"

As a more concrete example, an attacker could open an LP position worth $100. They would then manipulate price so that their leveraged position is now worth $100,000,000.

After borrowing $50,000,000 to lever up further and deposit more liquidity into the LP, they could sell at inflated exchange rate outside draining the $50,000,000 borrowed and any initial capital used to manipulate.

Patch

This exploit is similar to some vulnerabilities which have happened on Ethereum involving flash loans and on chain oracles. To mitigate the issue Alpha Finance introduced the concept of "fair pricing" for constant product LP tokens.

The concept is relatively simple. Instead of relying on the ratio of the tokens in the liquidity pool, compute what the ratio "should be" based on the relative price of the assets as determined by a trusted offchain oracle. This avoids using onchain data for pricing, which is susceptible to manipulation.

An abridged computation can be found below. For more details, we recommend reading the Alpha Finance article.

This concept also extends to stable curves but there is no closed form solution.

Closing Thoughts

While taken separately these components might seem innocuous, combined they allow for clever manipulation attacks. As with many security issues, a deep understanding of the underlying subsystem is required to discover potential inconsistencies.

This attack has many similarities to the warp finance exploit on Ethereum which also took advantage of LP tokens as collateral. DeFi developers should keep track of exploits everywhere, even ones that don't occur on their chain.

We would also like to thank the following teams for their fast triage and response.

1/02Apricot is notified
1/02Switchboard is notified
1/02Apricot triages report
1/02Switchboard triages report
1/06Tulip is notified
1/07Tulip releases patch
1/08Port is notified
1/08Port triages report
1/20Switchboard releases patch (Port covered)
1/21Apricot releases patch