Section 4 of 16
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)multipliesyby2^112, shifting it into the upper 112 bits. The result is auint224that represents the integerywith full fractional precision available.uqdiv(uint224 x, uint112 y)divides the encoded value by a rawuint112. 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:
- Overflow check: Require that both balances fit in
uint112. If a token's balance exceedstype(uint112).max, something has gone very wrong. - Timestamp: Read
block.timestampmodulo2^32(to fit in theuint32slot). CalculatetimeElapsedsince the last update. - Price accumulation (unchecked block): If time has passed and both reserves are nonzero, accumulate both price directions.
- Store: Write the new reserves and timestamp to storage.
- 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.