Section 4 of 16

Build
+15 Lynx

Pair: Reserve Updates and TWAP Oracle

What You Are Building

The _update() function is called after every mint, burn, and swap. It does two things: store the new reserves and accumulate prices for the TWAP (time-weighted average price) oracle. This is one of the most elegant pieces of Uniswap V2. A few lines of code give the entire DeFi ecosystem a manipulation-resistant price feed.

How the TWAP Oracle Works

The idea is simple. At every block where someone interacts with the pair, the contract records how long the previous price lasted and adds it to a running total. Any external contract can then read the cumulative price at two different times and compute the average price over that window.

The formula for accumulation:

price0CumulativeLast += (reserve1 / reserve0) * timeElapsed
price1CumulativeLast += (reserve0 / reserve1) * timeElapsed

But division in Solidity has no decimals. So Uniswap V2 uses a fixed-point format called UQ112x112.

The UQ112x112 Library

UQ112x112 encodes a number with 112 bits of integer part and 112 bits of fractional part. The total is 224 bits, which fits in a uint224.

Two functions:

  • encode(uint112 y) multiplies y by 2^112, shifting it into the upper 112 bits. The result is a uint224 that represents the integer y with full fractional precision available.
  • uqdiv(uint224 x, uint112 y) divides the encoded value by a raw uint112. The result is still in UQ112x112 format, preserving fractional precision.

So encode(reserve1).uqdiv(reserve0) gives you reserve1 / reserve0 with 112 bits of fractional precision. This is then cast to uint256 and multiplied by timeElapsed.

Why Overflow Is Intentional

The cumulative price values are uint256 and they are designed to overflow. This is safe because the oracle consumer only cares about the difference between two cumulative values. As long as the observation window is shorter than the overflow period (which is astronomically long), the difference is correct even if the values wrapped around.

In Solidity 0.8.x, arithmetic overflow reverts by default. The original Uniswap V2 code relied on Solidity 0.5's wrapping behavior. In our modernized version, we use unchecked blocks for the price accumulation to preserve this intentional overflow behavior. The reserve overflow check uses checked arithmetic (the require statement), but the TWAP accumulation must be unchecked.

The _update() Function

Here is what _update() does step by step:

  1. Overflow check: Require that both balances fit in uint112. If a token's balance exceeds type(uint112).max, something has gone very wrong.
  2. Timestamp: Read block.timestamp modulo 2^32 (to fit in the uint32 slot). Calculate timeElapsed since the last update.
  3. Price accumulation (unchecked block): If time has passed and both reserves are nonzero, accumulate both price directions.
  4. Store: Write the new reserves and timestamp to storage.
  5. Emit Sync: This event tells off-chain indexers the current reserves.

Your Task

The starter code gives you the UQ112x112 library and the _update() function signature. You need to write the complete function body. Pay careful attention to the unchecked block around price accumulation, the timestamp truncation, and the require statement for overflow protection.

Your Code

Solution.sol
Solidity
Loading editor...

Requirements

_update checks balance overflow
_update truncates block.timestamp to uint32
_update calculates time elapsed
_update checks timeElapsed and reserves before accumulation
_update uses UQ112x112.encode for price accumulation
_update uses unchecked block for price accumulation
_update accumulates price0CumulativeLast
_update accumulates price1CumulativeLast
_update stores new reserves
_update stores blockTimestampLast
_update emits Sync