A price oracle is a tool used to view price information about a given asset. When building smart contracts that integrate with DeFi protocols, developers inevitably run into the oracle problem. Uniswap v2 enabled developers to build highly decentralized and manipulation-resistant on-chain price oracles - time-weighted average price (TWAP) oracles - to help solve many of the demands necessary for building robust protocols. Uniswap v3 expanded this with added calculation and gas efficiency of TWAP oracles.
These tools are an important part of the ecosystem, because they are a trustless price feed that many smart contracts can plug into for price data. However, they are a huge target for potential bad actors because there is an economic incentive to manipulate them. Uniswap's TWAP oracles were designed to operate in a Proof of Work (PoW) mode, and their security was based on the fact that manipulators would lose money due to back-runs.
The transition from PoW to Proof of Stake (PoS) was the single largest change to block-building in Ethereum history. While it had many far reaching benefits for the ecosystem as a whole, it had unintended consequences on TWAP oracles (and some other mechanisms that relied on the random nature of the next block creator in PoW).
With the adoption of PoS, oracles are theoretically less secure because a malicious validator knows whether they control the next block - so they no longer lose the money to back-runs. But this comes with important caveats. Manipulation on most Uniswap v3 TWAP oracles isn't currently feasible, because bad actors both need to source incredibly high levels of capital and then need to make enough money back to make up for money lost to fees. Validators with enough market share to make an attack more likely are unlikely to do so because they would undermine their place in the market and users' confidence in their systems as neutral parties.
But due to this potential we want to explore the difficulty, potential cost, and likelihood of oracle manipulations on Uniswap v3 in a PoS environment. We also look at a series of potential solutions that could be made now as well as in future versions of the Protocol. We hope this research will help clarify the public perception of this issue as well as Uniswap Labs' work on oracles in the future.
- Two block Uniswap v3 TWAP oracle manipulation is still prohibitively expensive on top pairs in a PoS environment. Potential manipulators need to move the price by 71,721,000,000,000% for a single block to change the 30-min TWAP oracle price by 20%, which requires sourcing an infeasible amount of assets and losing a massive amount of it to fees 2
- Three block and more oracle manipulations are more feasible, but the validator would require large market share to make this likely.
- There is currently not enough economic incentive to manipulate TWAP, but it is possible that the incentive materializes if usage and composability on Uniswap v3 continue to grow.
- Wide-range liquidity is currently the best possible mitigation tool of oracle manipulations. Adding a single $1m wide-range mint on USDC/WETH 5 bps makes the two block oracle attack cost around $360b more.
- More robust oracle mechanics should be researched. For example, TWMP (Time weighted median price) oracles would make the manipulations impossibly expensive, requiring that pools be manipulated for over half the blocks in the period. Winsorized oracles are potentially another alternative, retaining most of the benefits of TWAP with added resistance.
The PoS (Proof of Stake) merge on mainnet Ethereum is the single largest change to the way blocks are constructed in Ethereum history.
One part of this change is that Proof of Stake block proposers know one epoch (32 blocks or 6 minutes and 24 seconds) ahead if they are the next block proposer. This means that validators and the entire network know who will propose the current epoch (at most 6 minutes and 24 seconds) and the next epoch (6 minutes and 24 seconds). This totals at least 6 minutes and 24 seconds and at most 12 minutes and 48 seconds with known block proposers. This is a material change from PoW (Proof of Work), because the stochastic nature of PoW makes it impossible to know for sure who will mine the next block. This deterministic knowledge of the next block proposer removes one of the major defenses to TWAP oracles.
Now, two or more block manipulations are theoretically possible. In PoS, validators control transaction sequencing. A validator controlling two consecutive blocks can greatly move the price of a pool in the first block and move it back in the next block without risk of being arbitraged against. This way the oracle based on this pool will have a data point of manipulated price, and the only cost to the manipulator would be 2 times the pool fee.
TWAP stands for “Time Weighted Average Price”. TWAP was added in Uniswap v2 and improved in Uniswap v3. TWAP on Uniswap v3 calculates the geometric mean of relative prices of the two assets in a pool. The TWAP oracle feed is used by protocols as a reference price for on-chain assets. Many protocols struggle to calculate on-chain prices that are both reliable and accurate. Protocols need to ensure they always can receive pricing information, but also want to make sure that it is accurate.
The transition from PoW to PoS changed some fundamental considerations for TWAP oracles. In PoW, the next miner was unknown until the block was accepted by the network. Potential manipulators could not guarantee that they would have the ability to cheaply back-run their own transaction. The most likely case in PoW was that manipulators would need to compete in an open auction for the opportunity to back-run their own manipulation, which would force them to pay market price for the opportunity. PoW ensured that manipulators lost value to a back-run. As long as protocols that consumed TWAP data ensured that the value lost to back-running outweighed the value gained from manipulating the pool, they were safe. However, manipulators can now ensure they back-run their own manipulation.
Many protocols require the current on-chain price of assets to calculate the market price of their portfolio. Perhaps the most relevant example is lending protocols, which must calculate the value of debt and collateral for loans issued by the protocol. Lending protocols use these values to effectively liquidate loans before they become undercollateralized. A potential attack vector for lending protocols is for an attacker to borrow assets that are immediately undercollateralized, by making the protocol overvalue the collateral that the attacker puts in. The protocol is then unable to liquidate and recoup the value of the borrowed assets, creating bad debt for the lending protocol and enriching the attacker.
Because spot prices are easy and relatively cheap to manipulate, most protocols use a rolling 30-minute time window for TWAP to calculate the price3. Using TWAP with a long window causes prices to be smooth but lagging. Lag is problematic if the spot prices naturally jump enough to cause an arbitrage of minting bad debt to the protocol, but realistically assets used as collateral should be scoped to ensure this is not realistic.
TWAP is more costly to manipulate than spot prices. Since most lending protocols require overcollateralization, to create bad debt, a malicious user needs to manipulate the TWAP oracle by the overcollateralization margin (unique to each asset and protocol, but 20% for our analysis). Because of this, we decided to calculate the cost of manipulating the Uniswap v3 TWAP oracle.
A manipulator can create a manipulated oracle update by swapping a large amount of one asset into the pool in block one, then swap the same amount in the opposite direction ('back-run') in block two. This will create an oracle update at the final manipulated price of block one. We define this two block set-up as a two block manipulation. It is possible to wait more blocks before back-running the initial manipulated swap. If a user waits two blocks before back-running their manipulated swap, then it is a three block manipulation (two blocks of waiting + one block of back-running to fire the oracle update). Since most protocols use a 30 minute running TWAP, waiting more blocks allows more of the total weighting on the final manipulated price.
Based on the amount of blocks the manipulation takes up, we can deterministically calculate the cost of a TWAP oracle manipulation of that length 4.
Ticks map to prices in Uniswap v3 by taking . is the unmanipulated 30 minute - (12 seconds * k) TWAP price. For more information about how we derive this formula, see the appendix.
Figure 1. Required ticks vs. block length manipulation for a 20% oracle manipulation
This graph shows the amount of ticks that must be crossed to cause a 20% oracle manipulation using Equation with
Figure 1 follows directly from Equation 1 using . At the peak, 273,497 ticks need to be crossed to manipulate the price by 20% if the TWAP is currently 0. After 10 blocks, this cost goes down to 27,350 ticks. This value exponentially decreases, requiring significantly less ticks to be crossed, making the manipulation much cheaper the more blocks that can be controlled.
As previously stated, PoS validators know how many sets of blocks they will propose for up to the next two epochs. Because of this, they can execute multiple oracle attacks disjointed as if they were one joined attack. Two sets of two-block oracle attacks can cross the same amount of ticks as one three-block attack. These attacks will have the equivalent impact, just costing more LP fees. This makes oracle attacks more likely.
From our previous equation, we know that manipulators need to cross 273,497 ticks to manipulate the TWAP price by 20%, but how many assets does this require in practice?
Table 1. Underlying required to create a two block manipulation
Sample taken September 4th, 2022 00:00 UTC.
For example, the amount of token 0 in the table above is the amount of token 0 needed to cross -273,497 ticks, which would increase the reference price of token 1 in the pool by 20%. For the USDC/WETH 5 bps pool, token 0 is USDC, and a swap of 709 billion USDC manipulates the reference TWAP price of WETH by 20%.
From Table 1, the cost of a two block manipulation for the pools is far too expensive to ever be done. For most pools, there are not enough underlying assets in circulation to even attempt a two block manipulation. Further, the cost lost to fees is more than the GDP of American Samoa 5
The incredible cost of a two block manipulation comes from wide-range liquidity. Significant liquidity exists at the outer ticks of most major pools. This comes from passive LPs, who choose wide-ranges in exchange for never rebalancing, and benevolent actors who want to improve oracle stability.
For example, at the final tick the manipulated swap must go through on (tick 476,496) in the 5 bps USDC/WETH pool, the manipulator must provide 45,138 WETH at a price of 2.1 x 10^-9 USDC each to swap through the final tick needed to successfully execute the manipulation. All the liquidity at the tick cost around $70 total in assets to place. With more wide-range liquidity, the manipulation exponentially increases in price. We will explore this in later sections.
Figure 2. Cost of two block manipulation over time
This graph shows a time series of the amount of WETH needed to manipulate the USDC price by 20% at each sample period. The sample is weekly from Sept 2021 to Sept 2022.
As more liquidity is onboarded to the pool, the price of 20% TWAP manipulation also increases significantly. Surprisingly, the cost doesn't drop significantly in the volatile period in July. While significant liquidity went out-of-range from the volatile period and spot liquidity became increasingly thin, the important wide-range liquidity did not pull from the pool. As a result, the cost of manipulation is generally increasing.
Most people who are concerned about the TWAP oracle manipulations are concerned about the case. This is because multiple block manipulations are now more feasible with the transition to PoS. First, let's look at only the cost of multi-block manipulations for the USDC/WETH 5 bps pool.
Table 2. Cost of manipulation for USDC/WETH 5 bps
Sample taken September 4th, 2022 00:00 UTC.
The drop from the two to the three block manipulation is staggering. While $978 million is still prohibitively expensive, it is not entirely unfeasible like $710 billion. This steep drop occurs because swapping through fewer total ticks is exponentially cheaper. With more blocks to manipulate, the amount of total ticks that must be crossed also rapidly decreases.
Pulling from this paper by Revuelta6, on average we should expect a block proposer with around 1% share to be assigned to propose three blocks in a row 0.19 times per month7. This means that a 1% block proposer would receive three blocks in a row on average once every 5 months. This greatly increases with more percent share. We should expect a validator with 10% market share to have three blocks in a row 181 times a month.
As previously mentioned, multiple disjoint sets of oracle manipulations can function the same as one longer set. Because large validators can also expect multiple two-block sets in the two known epochs, this will increase the likelihood that larger validators can oracle manipulate the pools. Because of this, the current findings are a lower-bound for the likelihood of oracle manipulations, and the real probability is higher.
Before discussing modifications, we want to clarify that Uniswap v3 is non-upgradable. This means that the current code cannot be changed at all, which makes it impossible to add new oracle types to the existing contracts. The Uniswap v3 contracts were designed to be non-upgradable, because of the security benefits to the users of the protocol. Non-upgradable contracts mean that users can trust the contracts that are deployed are the same and can ensure the behavior of the contracts always remains consistent. Upgradable contracts also pose a threat to decentralization.
With this, the solutions presented below require no code additions to the existing Uniswap v3 Protocol.
First, we looked at adding wide-range liquidity. Wide-range liquidity in v3 still returns a respectable yield when compared to Uniswap v2, so it is a feasible solution8. A full-range v3 WETH/USDC 30 bps position returns around 66% as much as the v2 position. A two block manipulation to move the 30 minute TWAP by 20% must cross 273,496 ticks, so any liquidity more than 273,496 ticks from the current tick is not utilized in mitigating oracle manipulations. The liquidity at tick 273,497 is unused, because liquidity will only kick in once the tick has been reached. Because liquidity closest to the final ticks of a manipulation is the most expensive, we want to ensure we are as close as possible to the final ticks with as few ticks over the required amount of ticks.
The optimal wide-range tick lower and upper is [current tick - 273,496 ticks, current tick + 273,496 ticks]. The current tick of Uniswap v3 WETH/USDC 5 bps is around 206,000, so the optimal range is [-67,496, 479,496]. However, all assets have some various degrees of volatility and liquidity can only be placed at specific ticks according to tick-spacing, so a wider-range than perfectly optimal should be chosen to account for the drift.
Table 3. Added wide-range liquidity position
Table 3 shows the added liquidity position for our analysis. The added position costs around $1,240,000 and should have minimal divergence loss compared to a concentrated position (due to the large range of the position). The tick range mapped to the position is from $1.9x10^-10 to $2.2x10^16 for each WETH.
Table 4. Cost of manipulation for USDC/WETH 5 bps with added position
Sample taken September 4th, 2022 00:00 UTC.
The position would make $540 million in fees for the LP if a two-block manipulation was executed. If a validator with a 1% validator share executed the amount of transactions to meet the expectation for the three block case, the LP would make $58 million in fees.
Because the added liquidity is very wide, the benefit to hindering potential manipulations is mainly in the two and three block cases. Most of the power is in the far edges, which manipulations with more blocks will not touch. The impact of this position is vast. A benevolent party could mint this position and trade out some potential yield from a concentrated liquidity for TWAP oracle stability.
Limit orders in Uniswap v3 are concentrated orders generally with a tick-range (tick-upper minus tick-lower) of one tick-spacing. Unlike central limit order books (CLOBs), limit orders on Uniswap v3 are double-sided, where once a buy order for one asset is traded against, a sell order for the other asset is placed at the same price. There are 3rd party services that will remove your orders for you automatically and create single-sided limit orders. A third party service tracks the pool and submits a transaction to burn your liquidity if certain conditions are met. The issue is that a manipulator would control transactions, and would add that transaction to their block to burn your limit order.
Since the exact tick needed for TWAP manipulation can be deterministically found at any time, a benevolent party could also place large limit orders right before the required amount of ticks to manipulate. This is not reasonable, because some assets have high volatility and would require frequent re-adjusting. Another problem with this strategy is the high opportunity cost of the orders. Below we analyze the impact of extreme out-of-range limit orders that still account for asset drift.
Unlike wide-range liquidity which is always in-range, limit orders only impact the cost of manipulation if their tick is reached. By setting the ticks farther from the current price, the manipulation becomes progressively expensive. On the other hand, if we place limit orders farther out, the limit orders impact fewer manipulations, since fewer ticks are crossed for those.
Say we wanted to impact four block manipulations. As previously stated, the current tick at the time of analysis was around 206,000, and 68,374 ticks must be crossed for the four block manipulation. To impact four block manipulations, theoretical limit orders must be placed inside the tick-range of [206,000-68,374, 206,000+68,374] = [137,626, 274,374]. To account for some asset drift, we placed limit orders valued around $100,000 at tick 150,000 and tick 250,000.
Table 5. Added limit order liquidity position
Adding a 100,000 USDC limit order at tick 250,000 (a buy order of WETH for 13.91 in USDC per WETH) and a 65 WETH limit order at tick 150,000 (a sell order of WETH at 306,131.82 USDC per WETH) adds some additional cost to the attack. The 100,000 USDC limit order makes the attack cost about $10,800,000 more. The WETH limit order adds about $19,900,000 to the cost. This is not as extreme as the wide-range order.
Instead of targeting four block manipulations, we could target two or three block manipulations. To compare the impact of changing the tick-range to mitigate only target two and three block manipulations, we also calculated the cost of manipulation if the order in Table 6 is created.
Table 6. Added wider limit order liquidity position
With these added limit orders, the three block manipulation costs $20.4 billion for WETH and $23.4 billion for USDC. Just like the two block manipulation, the three block is now impossible. However because we are out of the required range to impact four block manipulations, there is no change in their cost from the original calculations.
Neither full-range liquidity or double-sided limit orders fix the problem if a validator has enough market share. With enough market share, validators could execute twenty to thirty block manipulations, which are cheaper to execute. These orders only make the attack slightly more expensive.
Future research should be pursued into mitigation strategies for both the protocols that consume TWAP data and protocols that create it (i.e. the Uniswap Protocol). Research around median price oracles and winsorized (or similar) TWAP oracles should be done to create the next generation of PoS manipulation resistant oracles9.
Instead of traditional winsorize (where a potentially expensive rolling calculation of variance is needed), TWAP oracle updates could instead truncate if the update exceeds a total change from the previous oracle update. This creates oracle manipulation resistance up to a certain length of blocks. For example, if 20,000 ticks were crossed from the previous update, we could truncate that value down to potentially 9,116 ticks. A max value of 9,116 ensures that validators must execute at least a 30 block oracle manipulation in order to move 30 minute TWAP by 20%10. This max change in ticks could be lower or higher depending on further research.
Figure 3. Expected Number of Two Block Pairs by Validator Share
Calculated using Monte Carlo simulations
For example, a 30 block manipulation requires high validator market share. As seen above, a validator needs a share of 40% to expect a 30 block manipulation in a 150 block period. For a share of 30%, validators can only expect 1 in 2,000 probability of a 30 block manipulation during a 150 block period. For context, Lido has the highest share of validators with around 30%11.
A trade-off is that truncated TWAP oracles will update slower than current implementations. Limiting oracle updates to 9,116 in max changes will allow at most 2.5x price changes from block to block in TWAP. However 2.5x is a large enough change to still allow quick price convergence for most assets
Removing the look-ahead for proposers should also be considered, as this completely eliminates the manipulation problem. However, it would be non-trivial from the Ethereum perspective. The one-epoch look ahead was implemented to allow validators to join the right p2p network subnets and prepare for validation according to Ben Edgington12. The current best candidate technologies to help mitigate the problem are single slot finality or SSLE (secret single leader election)13. There is no guarantee that these will come any time soon to fix the problem.
Another proposal is for pool-implemented single sided limit orders. These are orders that burn from the pool without requiring outside intervention and cannot be blocked from executing by a malicious validator. The manipulator could not risk-free back-run their own manipulation to gain back all the lost capital from their initial swap. With this, the costs required to manipulate the pool would also include capital loss increasing the deterrent for manipulators.
Protocols use the Uniswap v3 TWAP oracle to consume market information about asset prices. With the merge from PoW to PoS, one of the main defenses of TWAP oracles was eliminated by allowing look-ahead for subsequent block proposers. While the two-block TWAP oracle manipulation is still prohibitively expensive, three-block and greater attacks are technically feasible, though statistically very unlikely for validators with a smaller share. We have shown several ways to make these attacks more unrealistic, but all have trade-offs that must be considered.
Uniswap Labs is currently researching PoS resistant oracle implementations, but other improvements such as more wide-range liquidity and limit orders could be introduced. We hope that this blog has shed some light on the potential feasibility of TWAP oracle manipulation on Uniswap v3.
Below is a refresher on the implementation of TWAP on Uniswap v3.
TWAP on Uniswap v3 is implemented by calculating the time weighted tick of the Uniswap v3 pool at the end of the block, then mapping that tick to price. The weighted tick can be turned into a by taking .
If a pool was at tick from time to , an oracle update is created once the tick moves at time .
In the Uniswap v3 smart contract, there is an accumulator that cumulatively sums these updates up to the current block14. To calculate TWAP from to , you need to subtract the accumulator at from and divide by .
However, this is only an optimization for gas efficiency. You can also just sum the tick . at each block even if the tick did not change, assuming the tick changes in a future block from t0. However, it is very important to remember that an oracle update only fires if the current tick changes. Otherwise the oracle is linearly interpolated and may yield different results, and the below approximation will be incorrect. We make the assumption for our analysis that a potential manipulator will back-run their swap, thus guaranteeing an oracle update. We calculate TWAP from to using this formula.
We thank Teo Leibowitz, Mark Toda, Sara Reynolds, Will Pote, and Aseem Sood for their comments. ↩
It is important to note that an oracle update at the non-manipulated price will also fire when the initial manipulated swap occurs. Thus potential manipulators only have the ability to control the price for one block less than their total manipulation length. Let be the amount of blocks that the manipulator controls the oracle updates for (which is one less than the length of their total manipulation length). If a potential manipulator wants to manipulate the 30 minute (N = 150 blocks) TWAP price by percent, then they need to cross ticks defined by Equation 1 ↩
This can be calculated directly from Equation 1 by using k=30 and delta = .2. The TWAP value subtracts, so it can be set to 0. ↩
Secret single slot election doesn't fully mitigate the problem as validators know where they personally are chosen in the next epoch. However, it makes the attack more difficult to coordinate. ↩
Generally, we think of TWAP as the geometric mean of prices, but the equation below describes the arithmetic mean of prices. It is important to note that the geometric and arithmetic mean are equivalent in log space. ↩