What is Price#
As a decentralized exchange on the blockchain, Uniswap carries out 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 for this purpose.
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 significant fluctuations during a flash loan transaction. In Uniswap V2, the Time Weighted Average Price (TWAP) mechanism is used to address this issue.
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 the 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 the 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 certain time interval. This is the historical price formula for TWAP:
Assuming our pricing starts from T4, the actual pricing 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 pricing variable price0CumulativeLast for token0:
This variable records the sum of all time periods since its inception. We only need the portion from T4 onwards, 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 T6 timestamp. The difference between the two snapshots is the pricing of token0 within the T4-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
. - Only when
timeElapsed
> 0, indicating the entry of a new block, will the price * time period be accumulated. - Note that the price calculation uses the special format
UQ112x112
to handle decimals. Understanding it is only necessary for recording decimals, and it will be explained later. - After the accumulation 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 costly.
- When there are significant price 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 may provide outdated prices, especially during market turbulence. This can result in a significant difference between the prices in Uniswap and the external market.
- The use of TWAP oracles still relies on off-chain triggers, which introduces maintenance costs and centralization issues.