Better Price Feeds With Uniswap v2

Understanding Uniswap v2 Price Feeds

Ishan Shahzad
Coinmonks
Published in
13 min readSep 2, 2022

--

Hello, today we’re going to talk about Uniswap v2 price feeds and how Uniswap v2 offers better price feeds for you.

The rapidly growing decentralized finance (DeFi) ecosystem aims to use decentralized, non-custodial financial products to replace centralized middlemen in financial applications such as loans, insurance and derivatives.

Uniswap is an example of one of the core products in the DeFi ecosystem, the decentralized crypto exchange, or DEX. First, let’s discuss what Uniswap is.

What is Uniswap?

Uniswap is a protocol on Ethereum for swapping ERC20 tokens without the need for buyers and sellers to create demand. In other words, Uniswap is an Ethereum-based decentralized exchange (DEX) that allows anyone to swap ERC20 tokens. In September 2020, Uniswap launched its UNI governance token with an airdrop to anyone who had used the protocol before September. Uniswap V3 launched in May 2021, adding new features including concentrated liquidity and multiple fee tiers.

Unlike most exchanges, which are designed to take fees, Uniswap functions as a public good — a tool for the community to trade tokens without platform fees or middlemen. Also unlike most exchanges, which match buyers and sellers to determine prices and execute trades, Uniswap uses a simple math equation and pools of tokens and ETH to do the same job.

Uniswap v1 vs v2

Let's remind ourselves of its evolutionary path and the difference between v1 and v2. It will help us understand why the new features are the only natural thing that could happen for Uniswap to be one of the leaders in the DeFi space.

Uniswap v1 — Introducing AMMs

Uniswap v1 was first launched on the Ethereum mainnet on November 2, 2018. Even though it wasn’t the first DEX, it certainly was the first one to catch the eye of Crypto lovers. Before Uniswap, EtherDelta was pretty much the only DEX that captured some traction. Still, it was based on the Order Book model, which isn’t considered the best solution for a decentralized exchange because it introduces problems such as costs, poor user experience and a lack of liquidity.

Unlike EtherDelta, Uniswap is based on the Automated Market Maker (AMM) model. This model relies on a mathematical formula to price assets. Rather than placing orders, AMMs rely on Liquidity Providers (LPs) who invest in trading pairs in Liquidity Pools.

Uniswap is a Constant Function Market Maker, or more specifically, a Constant Product Market Maker. That means that the ratio of trading pairs in every liquidity pool must respect the Constant Product Formula:

x*y=k

where k is a constant, x is the reserve of the first asset, and y is the reserve of the second asset.

This means that all the LPs are to provide additional liquidity in a way that wouldn’t change k. Also, everyone traded had to be aware of the total amount of the funds locked to avoid high slippage.

Uniswap v1 supported only ETH-ERC20 trading pairs, so you could only swap ETH for a single ERC20 token. So, if you wanted to swap USDC for DAI, you had to swap USDC for ETH and then go to the ETH-DAI pool to get DAI.

Uniswap v1 Example — DAI to USDC Swap
Uniswap v1 Example — DAI to USDC Swap

Another feature Uniswap v1 introduced was LP tokens. Every liquidity provider would get the amount of LP token proportional to the percentage of total liquidity they added. These LP tokens represent LP’s contribution to the pool and could be sold/traded or burned to redeem the deposited tokens. Furthermore, every trade on Uniswap incurred a 0.30% trading fee. These fees were automatically sent to the liquidity reserve as a reward for LPs providing liquidity.

Uniswap v2 — Optimization, Decentralization and Security

Uniswap v1 was a proof-of-concept for a new type of decentralized marketplace. Its initial success was a signal for the team to immediately reiterate an even better solution. Uniswap v2 was launched in May 2020.

In short, Uniswap v2 was a much better and more user-friendly version of Uniswap v1. The main problem of v1 addressed in this new version was the absence of ERC20-ERC20 token pools. This incurred much higher costs and slippage for users who wanted to swap one ERC20 token for another.

ERC20-ERC20 Pools

Uniswap v2 introduced ERC20-ERC20 liquidity pools to solve this “ETH bridging” problem. They even used Wrapped ETH instead of native ETH in the core contracts. However, end-users could still use ETH through helper contracts.

Uniswap V2 — ETH Bridging
Uniswap V2 — ETH Bridging

Oracles

This second version of Uniswap also implemented new functionality that enables highly decentralized and manipulation-resistant on-chain price feeds.

The basic idea was to calculate the average price over a period of blocks (Time Weighted Average Price — TWAP) by dividing the cumulative price (sum of the Uniswap price for every second in the entire history of the contract) by the timestamp duration (the end-of-duration timestamp minus the start-of-duration timestamp).

Uniswap v2 — Time Weighted Average Price — TWAP
Uniswap v2 — Time Weighted Average Price — TWAP

Flash Swaps

Another groundbreaking feature that v2 introduced was Flash Swaps. These Flash Swaps allowed users to withdraw as much as they wanted of any ERC20 token on Uniswap at no upfront cost and do anything they wanted with them (execute arbitrary code), provided that by the end of the transaction execution, they either:

  • pay for all ERC20 tokens withdrawn
  • pay for a percentage of ERC20 tokens and return the rest
  • return all ERC20 tokens withdrawn
Uniswap v2 Flash Swaps
Uniswap v2 Flash Swaps

Uniswap also introduced a protocol fee (that could be turned on/off via a decentralized community vote that sends 0.05% of every 0.30% trading fee to a Uniswap fund to finance future development.

Even though Uniswap v1 still existed after v2 was launched, it didn’t take too long for LPs to transfer most of the liquidity from one version to another. Uniswap v2 experienced huge success, passing even some of the most famous centralized exchanges in daily trading volume. This success made Uniswap v2 one of the most forked projects — SushiSwap being its biggest competitor that was created as a fork. This vampire attack from SushiSwap drained a big portion of liquidity from Uniswap, which led to the famous UNI token airdrop in September 2020.

Uniswap V2 Offers Better Price Feeds

Uniswap v2 has released the second major version of the platform, which has the feature of a better price feed oracle while introducing a variant of the flash loan.

How are Uniswap Prices Determined?

As we learned in Uniswap Overview, each pair on Uniswap is actually underpinned by a liquidity pool. Liquidity pools are smart contracts that hold balances of two unique tokens and enforce rules around depositing and withdrawing them. The primary rule is the constant product formula. When a token is withdrawn (bought), a proportional amount must be deposited (sold) to maintain the constant. The ratio of tokens in the pool, in combination with the constant product formula, ultimately determines the price that a swap executes at.

How Uniswap Handles Prices

In Uniswap V1, trades are always executed at the “best possible” price, calculated at execution time. Somewhat confusingly, this calculation is actually accomplished with one of two different formulas, depending on whether the trade specifies an exact input or output amount. Functionally, the difference between these two functions is miniscule, but the very existence of a difference increases conceptual complexity. Initial attempts to support both functions in V2 proved inelegant, and the decision was made to not provide any pricing functions in the core. Instead, pairs directly check whether the invariant was satisfied (accounting for fees) after every trade. This means that rather than relying on a pricing function to also enforce the invariant, V2 pairs simply and transparently ensure their own safety, nice separation of concerns. One downstream benefit is that V2 pairs more naturally support other flavours of trades which emerge, (e.g. trading to a specific price at execution time).

At a high level, in Uniswap V2, trades must be priced in the periphery. The good news is that the library provides a variety of functions designed to make this quite simple, and all swapping functions in the router are designed with this in mind.

Pricing Trades

When swapping tokens on Uniswap, it’s common to want to receive as many output tokens as possible for an exact input amount, or to pay as few input tokens as possible for an exact output amount. In order to calculate these amounts, a contract must look up the current reserves of a pair, in order to understand what the current price is. However, it is not safe to perform this lookup and rely on the results without access to an external price.

Suppose, a smart contract naively wants to send 10 DAI to the DAI/WETH pair and receive as much WETH as it can get, given the current reserve ratio. If, when called, the naive smart contract simply looks up the current price and executes the trade, it is vulnerable to front-running and will likely suffer an economic loss. To see why to consider a malicious actor who sees this transaction before it is confirmed. They could execute a swap which dramatically changes the DAI/WETH price immediately before the naive swap goes through, wait for the naive swap to execute at a bad rate, and then swap to change the price back to what it was before the naive swap. This attack is fairly cheap and low-risk, and can typically be performed for a profit.

To prevent these types of attacks, it’s vital to submit swaps that have access to knowledge about the “fair” price their swap should execute at. In other words, swaps need access to an oracle, to be sure that the best execution they can get from Uniswap is close enough to what the oracle considers the “true” price. While this may sound complicated, the oracle can be as simple as an off-chain observation of the current market price of a pair. Because of arbitrage, it’s typically the case that the ratio of the intra-block reserves of a pair is close to the “true” market price. So, if a user submits a trade with this knowledge in mind, they can ensure that the losses due to front-running are tightly bounded. This is how, for example, the Uniswap front end ensures trade safety. It calculates the optimal input/output amounts given observed intra-block prices and uses the router to perform the swap, which guarantees the swap will execute at a rate no less than x% worse than the observed intra-block rate, where x is a user-specified slippage tolerance (0.5% by default).

Exact Input

If you’d like to send an exact amount of input tokens in exchange for as many output tokens as possible, you’ll want to use getAmountsOut. The equivalent SDK function is getOutputAmount, or minimumAmountOut for slippage calculations.

Exact Output

If you’d like to receive an exact amount of output tokens for as few input tokens as possible, you’ll want to use getAmountsIn. The equivalent SDK function is getInputAmount, or maximumAmountIn for slippage calculations.

Price Oracles

Uniswap V2 implemented new functionality that enables highly decentralized and manipulation-resistant on-chain price feeds. This is achieved by measuring prices when they are expensive to manipulate, and cleverly accumulating historical data. This allows external smart contracts to create gas-efficient, time-weighted averages of Uniswap prices across any time interval.

On-chain price feeds are a critical component for many decentralized financial applications including those similar to derivatives, lending, margin trading, prediction markets and more. Despite closely tracking the real-world price most of the time, Uniswap V1 cannot be used safely as a price oracle because the price can move significantly in a short period of time.

Uniswap V2 includes a number of improvements for price feed built on top of it. First, every pair measures (but does not store) the market price at the beginning of each block, before any trades take place. This price is expensive to manipulate because it was set by the last transaction in a previous block.

To set the measured price to one that is out of sync with the global market price, an attacker has to make a bad trade at the end of a previous block, typically with no guarantee that they will be able to arbitrage it back in the next block. Attackers will lose money to arbitrageurs unless they can “selfishly” mine two blocks in a row. This type of attack presents a number of challenges and has not been observed to date.

This alone is not enough. If significant value settles based on the price resulting from this mechanism, then the profit of an attack likely can outweigh the loss.

Instead, Uniswap V2 adds this end-of-block price to a single cumulative-price variable in the core contract weighted by the time this price existed. This variable represents a sum of the Uniswap price for every second in the entire history of the contract.

Uniswap v2 Onchain Price Feeds
Uniswap v2 Onchain Price Feeds

This variable can be used by external contracts to track accurate time-weighted average prices (TWAPs) across any time interval.

This is done by reading the cumulative price from an ERC20 token pair at the beginning and at the end of the interval. The difference in this cumulative price can then be divided by the length of the interval to create a TWAP for that period.

TWAPs using Uniswap V2
TWAPs using Uniswap V2

TWAPs can be used directly or as the basis for moving averages (EMAs and SMAs) as needed.

A few notes:

  • For a 10-minute TWAP, sample once every 10 minutes. For a 1-week TWAP, sample once every week.
  • For a simple TWAP, the cost of manipulation increases (approx. linear) with liquidity on Uniswap, as well as (approx. linear) with the length of time over which you average.
  • Cost of an attack is relatively simple to estimate. Moving the price 5% on a 1-hour TWAP is approximately equal to the amount lost to arbitrage and fees for moving the price 5% every block for 1 hour.

There are some nuances that are good to be aware of when using Uniswap V2 as an oracle, especially where manipulation resistance is concerned.

Uniswap V2 Pricing

Let’s talk pricing. This guide will focus on the two most important Uniswap prices: the mid-price and the execution price.

Mid Price

The mid-price, in the context of Uniswap, is the price that reflects the ratio of reserves in one or more pairs. There are three ways we can think about this price.

  • It defines the relative value of one token in terms of the other.
  • It represents the price at which you could theoretically trade an infinitesimal amount of one token for the other.
  • It can be interpreted as the current market-clearing or fair value price of the assets.

Let’s consider the mid-price for DAI-WETH (that is, the amount of DAI per 1 WETH).

Direct

The simplest way to get the DAI-WETH mid-price is to observe the pair directly:

You may be wondering why we have to construct a route to get the mid-price, as opposed to simply getting it from the pair (which, after all, includes all the necessary data). The reason is simple: a route forces us to be opinionated about the direction of trading. Routes consist of one or more pairs, and an input token (which fully defines a trading path). In this case, we passed WETH as the input token, meaning we’re interested in a WETH -> DAI trade.

Now we understand that the mid-price is going to be defined in terms of DAI/WETH. Not to worry though, if we need the WETH/DAI price, we can easily invert it.

Finally, you may have noticed that we’re formatting the price to 6 significant digits. This is because internally, prices are stored as exact-precision fractions, which can be converted to other representations on demand.

Indirect

For the sake of example, let’s imagine a direct pair between DAI and WETH doesn’t exist. In order to get a DAI-WETH mid-price we’ll need to pick a valid route. Imagine both DAI and WETH have paired with a third token, USDC. In that case, we can calculate an indirect mid-price through the USDC pairs:

Execution Price

Mid prices are great representations of the current state of a route, but what about trades? It turns out that it makes sense to define another price, the execution price of a trade, as the ratio of assets sent/received.

Suppose, we’re interested in trading 1 WETH for DAI:

Notice that we’re constructing a trade of 1 WETH for as much DAI as possible, given the current reserves of the direct pair. The execution price represents the average DAI/WETH price for this trade. Of course, the reserves of any pair can change every block, which would affect the execution price.

Also notice that we’re able to access the next mid-price if the trade were to complete successfully before the reserves changed.

Wrap Up

This article explained the deep understanding of Uniswap v2 price feeds. Also, read my previous story on the overview of Uniswap v3 🔽

Stay Connected With Me! 👋

Muhammad Shahzad
VP of Technology at Renesis Tech

Linkedin ▶️ https://www.linkedin.com/in/ishanshahzad/

New to trading? Try crypto trading bots or copy trading

--

--