Skip to content

Commit

Permalink
Adding Reading Pool State guide to docs (#793)
Browse files Browse the repository at this point in the history
  • Loading branch information
vendrell46 authored Oct 10, 2024
1 parent 4d5d063 commit 6b00744
Showing 1 changed file with 257 additions and 3 deletions.
260 changes: 257 additions & 3 deletions docs/contracts/v4/guides/07-read-pool-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,262 @@
title: Reading Pool State
---

## 🚧 Under construction 🚧
## Introduction

Please see [StateLibrary](https://github.com/Uniswap/v4-core/blob/main/src/libraries/StateLibrary.sol)
Unlike previous versions, v4 uses a different approach for storing and accessing pool data, which requires understanding the use of [`StateLibrary`](/contracts/v4/reference/core/libraries/StateLibrary) and [`extsload`](/contracts/v4/reference/core/Extsload).

and how it's used in [tests](https://github.com/Uniswap/v4-core/blob/main/test/PoolManagerInitialize.t.sol#L91)
## Understanding the PoolManager Architecture

### The Singleton Design

In Uniswap v4, all pools are managed by a single `PoolManager` contract, unlike v3 where each pool was a separate contract. This design offers simplified management since all pools are now accessible through a single contract.

This approach significantly reduces deployment costs, simplifies protocol upgrades, and enables more efficient cross-pool interactions. It also allows for easier implementation of new features across all pools simultaneously.

### Pools as Library Calls

In v4, pools are stored as complex structs, with Solidity libraries handling state changes. The `PoolManager` contract uses these libraries to perform operations on the pool state:

```solidity
contract PoolManager {
using Pool for Pool.State;
mapping(PoolId => Pool.State) internal pools;
function swap(PoolId id, ...) external {
pools[id].swap(...); // Library call
}
}
```

This design allows all AMM logic to be encapsulated within the `PoolManager` contract.

## Reading Pool State in v4

In Uniswap v4, reading pool state involves a few key concepts and mechanisms that differ from previous versions. At the core of this new structure is a complex mapping within the PoolManager contract:

```solidity
mapping(PoolId id => Pool.State) internal _pools;
```

This mapping represents a fundamental shift in pool data storage:

1. Each pool is identified by a unique `PoolId`.
2. The `Pool.State` is a struct that contains all the state variables for a single pool.
3. This struct itself contains several nested mappings and complex data structures.

For example, the `Pool.State` struct might look something like this (simplified for illustration):

```solidity
struct State {
uint160 sqrtPriceX96;
int24 tick;
uint128 liquidity;
uint256 feeGrowthGlobal0X128;
uint256 feeGrowthGlobal1X128;
mapping(int24 => TickInfo) ticks;
mapping(bytes32 => Position.Info) positions;
// ... other fields
}
```

This complex structure allows for efficient storage of multiple pools and their associated data within a single contract. However, it also means that traditional getter functions would be inefficient or impractical for accessing this data, especially for nested mappings like `ticks` and `positions`.

To address this, Uniswap V4 introduces the StateLibrary and the concept of using `extsload` for reading pool state. These mechanisms provide efficient ways to access the data stored in this complex structure.

### The StateLibrary and `extsload`

```solidity
abstract contract Extsload is IExtsload {
/// @inheritdoc IExtsload
function extsload(bytes32 slot) external view returns (bytes32) {
assembly ("memory-safe") {
mstore(0, sload(slot))
return(0, 0x20)
}
}
// [...]
}
```

The `StateLibrary` is a crucial component in Uniswap v4 for reading pool state. It utilizes the `extsload` function, which is an external wrapper for the `SLOAD` opcode. This allows for efficient reading of arbitrary storage slots.

**How `extsload` works:**

- It takes a storage slot as input.
- It reads the value stored in that slot directly, using `SLOAD`, from the contract's storage.
- It returns the value as a `bytes32`.

This method is more gas-efficient than traditional getter functions, especially when reading multiple storage slots.

Moreover, using `extsload` instead of hand-written Solidity view functions lowers the contract bytecode size. This optimization is particularly important for Uniswap v4, as the core contracts are nearly at Ethereum's contract size limit.

### TransientStateLibrary and `exttload`

```solidity
abstract contract Extsload is IExtsload {
/// @inheritdoc IExtsload
function extsload(bytes32 slot) external view returns (bytes32) {
assembly ("memory-safe") {
mstore(0, sload(slot))
return(0, 0x20)
}
}
// [...]
}
```

While `StateLibrary` deals with persistent storage, [`TransientStateLibrary`](/contracts/v4/reference/core/libraries/transient-state-library) is used for handling transient storage. Transient storage, introduced in EIP-1153, is a way to store data that is only needed for the duration of a transaction, making it ideal for temporary data.

It uses the [`exttload`](/contracts/v4/reference/core/Exttload) function, which is similar to `extsload`, but for transient storage; it is an external wrapper for the `TLOAD` opcode.

## Implementing a `PoolStateReader` Contract

Let's create a `PoolStateReader` contract that showcases different methods for reading pool state. For each function, we'll explain its purpose, how it works, and provide an example use case.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/types/PoolId.sol";
contract PoolStateReader {
using PoolIdLibrary for PoolKey;
IPoolManager public immutable poolManager;
constructor(IPoolManager _poolManager) {
poolManager = _poolManager;
}
// Functions will be implemented here
}
```

Before we start, we need to import `StateLibrary` from the libraries available in v4-core.

```solidity
import {StateLibrary} from "v4-core/libraries/StateLibrary.sol";
```

Let's focus on this important line that we should add:

```solidity
using StateLibrary for IPoolManager;
```

This line is crucial for our PoolStateReader contract because it allows us to call StateLibrary functions as if they were methods of the IPoolManager interface, like for instance now we will be able to do `poolManager.getSlot0()`.

Now we’re up for breaking down each wrapper function that we're going to be adding to our helper contract, explain the purpose of the pool manager function to read the state, and provide use cases to make sure we understand its utility:

### `getSlot0()`

```solidity
function getPoolState(PoolKey calldata key) external view returns (
uint160 sqrtPriceX96,
int24 tick,
uint24 protocolFee,
uint24 lpFee
) {
return poolManager.getSlot0(key.toId());
}
```

**Explanation:**

This function retrieves the current state of the pool, including its price, tick, and fee settings. It uses the `getSlot0()` function from `StateLibrary`, which efficiently reads these values from a single storage slot.

- `sqrtPriceX96`: The current price, encoded as a square root and scaled by 2^96. This encoding allows for efficient price calculations in the Uniswap algorithm.
- `tick`: The current tick, representing the quantized price. Ticks are used to efficiently track and update liquidity positions.
- `protocolFee`: The current protocol fee, represented in hundredths of a bip (i.e., units of 0.0001%).
- `lpFee`: The current liquidity provider fee, also represented in hundredths of a bip.

**Use Case:**

This function is essential for any application that needs to know the current state of a Uniswap v4 pool. For example:

- A price oracle could use this to get the current price of the pool.
- A trading bot could use this to determine if a trade is profitable given the current price and fees.
- A liquidity management system could use the `tick` to decide where to place new liquidity.

### `getLiquidity()`

```solidity
function getPoolLiquidity(PoolKey calldata key) external view returns (uint128 liquidity) {
return poolManager.getLiquidity(key.toId());
}
```

**Explanation:**

This function retrieves the current total liquidity in the pool. Liquidity in Uniswap v4 represents the amount of assets available for trading within the current price range.

**Use Case:**

Understanding the total liquidity is crucial for several scenarios:

- Assessing the depth of the market and potential slippage for large trades.
- Monitoring the depth and growth of a pool over time.

### `getPositionInfo`

```solidity
function getPositionInfo(
PoolKey calldata key,
address owner,
int24 tickLower,
int24 tickUpper,
bytes32 salt
) external view returns (
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128
) {
return poolManager.getPositionInfo(key.toId(), owner, tickLower, tickUpper, bytes32(salt));
}
```

**Explanation:**

This function retrieves information about a specific liquidity position. It returns:

- `liquidity`: The amount of liquidity in the position.
- `feeGrowthInside0LastX128` and `feeGrowthInside1LastX128`: The last recorded cumulative fees earned per unit of liquidity for each token.

**Use Case:**

This function is crucial for applications that need to manage or analyze individual liquidity positions:

- A liquidity management dashboard could use this to display a user's current positions and earned fees.
- An automated liquidity provision system could use this to decide when to rebalance or compound rewards.
- An analytics tool could use this to calculate the performance of different liquidity provision strategies.

### `getFeeGrowthGlobal`

```solidity
function getFeeGrowthGlobal(PoolKey calldata key) external view returns (
uint256 feeGrowthGlobal0X128,
uint256 feeGrowthGlobal1X128
) {
return poolManager.getFeeGrowthGlobal(key.toId());
}
```

**Explanation:**

This function retrieves the global fee growth for both tokens in the pool. These values represent the cumulative fees per unit of liquidity since the pool's inception.

**Use Case:**

Global fee growth is essential for several advanced operations:

- Calculating the fees earned by a position that has been held for a long time.
- Analyzing the overall fee generation of a pool over its lifetime.
- Comparing the performance of different pools or fee tiers.

---

For additional reference, see [`StateLibrary`](/contracts/v4/reference/core/libraries/StateLibrary) and [`Extsload`](/contracts/v4/reference/core/Extsload)

0 comments on commit 6b00744

Please sign in to comment.