What is Price#
As a decentralized exchange on the blockchain, Uniswap carries the function of price discovery, where users or other on-chain contracts can obtain token prices through Uniswap. Uniswap serves as an on-chain oracle.
Assuming there is 1 Ether and 2000 USDC in the current trading pool, the price of Ether would be 2000/1 = 2000 USDC. Conversely, the price would be the price of USDC. Therefore, price is a ratio. Since smart contracts do not support decimals, Uniswap's code has expanded to include new data types to store prices, which will be explained later.
TWAP Price Mechanism#
However, using the instantaneous ratio of token amounts as the price is not secure, as there is a risk of manipulating the price oracle. Due to Uniswap's flash loan feature, the token balances within a trading pair can experience drastic fluctuations during a flash loan transaction. To address this issue in Uniswap V2, the Time Weighted Average Price (TWAP) mechanism is used as a price oracle.
The specific working principle is as follows:
- Assuming in the past 20 hours, the price of an asset was $20, and in the most recent 4 hours, the price was $10, then TWAP = ($20 * 20 + $10 * 4) / 24 = $18.33.
- Assuming in the past 1 hour, the price of an asset was $10, and in the most recent 23 hours, the price was $15, then TWAP = ($10 * 1 + $15 * 23) / 24 = $14.79.
In summary, the formula for TWAP is as follows, where T represents the time period and P represents the corresponding price:
In the Uniswap V2 contract, only the numerator part is recorded, which is the sum of each time period multiplied by the price. The denominator part needs to be maintained by the user. Since there are two tokens in a trading pair, there are two values to record.
Usually, we only need to be concerned with the token price within a specific time interval. This is the historical price formula for TWAP:
Assuming our valuation starts from T4, the actual valuation formula would be:
As mentioned earlier, there is a variable in the contract that tracks the sum of the numerator, using the example of the tracking valuation variable price0CumulativeLast for token0:
This variable records the sum of all time periods since the beginning. We only need the part starting from T4, and the calculation is simple. At the T3 timestamp, we take a snapshot of the price0CumulativeLast variable, and then take another snapshot at the latest timestamp, T6. The difference between the two snapshots represents the valuation of token0 within the T4 to the latest time period:
We also maintain the recent window duration, which is T4 + T5 + T6. The TWAP price for this time period can be calculated as (price0CumulativeLast - UpToTime3) / (T4 + T5 + T6).
Implementation in Uniswap V2#
In the specific implementation of Uniswap, two variables, price0CumulativeLast and price1CumulativeLast, are maintained for each trading pair. The summation is performed in the _update
method mentioned earlier. The specific code is as follows:
- First, the current block timestamp
blockTimestamp
is obtained. - By subtracting
blockTimestampLast
, the time elapsed since the last update is calculated astimeElapsed
. - The summation of price * time period is only performed when
timeElapsed
> 0, indicating that a new block has been entered. - Note that the price calculation uses a special format called
UQ112x112
to handle decimals. Understanding this optimization is sufficient, and it will be explained later. - After the summation is completed,
blockTimestampLast
is updated to the latest block timestamp.
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
...
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
blockTimestampLast = blockTimestamp;
...
}
Potential Issues with TWAP#
- Time-weighted price calculation increases the cost for attackers to manipulate the price, as they would need to control multiple blocks continuously, which is expensive.
- When prices experience drastic fluctuations, the price provided by the oracle cannot reflect the price changes in a short period of time due to the time-weighted factor. Instead, it provides outdated prices, especially during market turbulence. This can lead to significant differences between the prices in Uniswap and the external market.
- The use of TWAP oracles still relies on off-chain triggers, resulting in maintenance costs and centralization issues.