waterpump-evm-pool-sdk 0.1.0

EVM pool SDK — viewers, infusers, harvesters, swappers for Uniswap V3/V4, PancakeSwap, Slipstream, Shadow, Algebra
Documentation
use alloy::primitives::{
    aliases::{I24, U160, U24},
    B256, I256, U256,
};
use anyhow::Result;

/// Decode slot0 data from raw bytes
///
/// Slot0 is the first field in the State struct (at state_slot + 0):
/// ```
/// struct State {
///     Slot0 slot0;  // This field - contains price and tick information
///     ...
/// }
/// ```
///
/// Slot0 contains packed data:
/// - sqrt_price_x96 (U160): Current sqrt price in Q96 format, bytes 12-32
/// - tick (I24): Current tick value, bytes 9-12 (3 bytes, signed)
/// - protocol_fee (U24): Protocol fee rate, bytes 6-9 (3 bytes)
/// - lp_fee (U24): Liquidity provider fee rate, bytes 3-6 (3 bytes)
///
/// ## Arguments
/// * `data` - Raw 32-byte slot data from storage
///
/// ## Returns
/// A tuple of (sqrt_price_x96, tick, protocol_fee, lp_fee)
pub fn decode_slot0(data: &[u8]) -> Result<(U160, I24, U24, U24)> {
    let sqrt_price_x96 = U160::from_be_slice(&data[12..32]);

    let tick_bytes = unsafe { (data.as_ptr().add(9) as *const [u8; 3]).read_unaligned() };
    let tick = I24::from_be_bytes(tick_bytes);

    let protocol_fee_bytes = unsafe { (data.as_ptr().add(6) as *const [u8; 3]).read_unaligned() };
    let protocol_fee = U24::from_be_bytes(protocol_fee_bytes);

    let lp_fee_bytes = unsafe { (data.as_ptr().add(3) as *const [u8; 3]).read_unaligned() };
    let lp_fee = U24::from_be_bytes(lp_fee_bytes);

    Ok((sqrt_price_x96, tick, protocol_fee, lp_fee))
}

/// Decode tick_lower and tick_upper from packed V4 position info
///
/// This decodes position information from State.positions mapping:
/// ```
/// struct State {
///     ...
///     mapping(bytes32 => Position.State) positions;  // At state_slot + POSITIONS_OFFSET
/// }
/// ```
///
/// V4 positions store tick_lower and tick_upper in a packed U256 format (32
/// bytes). This function extracts both values from the packed representation:
/// - tick_lower: bits 232-255 (3 bytes, signed 24-bit integer)
/// - tick_upper: bits 208-231 (3 bytes, signed 24-bit integer)
///
/// The packing format allows both tick values to fit in a single storage slot.
///
/// ## Arguments
/// * `position_info` - Packed position info as U256 containing both tick_lower
///   and tick_upper
///
/// ## Returns
/// A tuple of (tick_lower, tick_upper) as signed 24-bit integers
pub fn decode_position_info(position_info: U256) -> (I24, I24) {
    let tick_lower = I256::from_raw(position_info << 224).asr(232);
    let tick_upper = I256::from_raw(position_info << 200).asr(232);
    (I24::from(tick_lower), I24::from(tick_upper))
}

/// Decode liquidity gross and net from a B256 word
///
/// This decodes data from the TickInfo struct stored in the State.ticks
/// mapping. Each tick's TickInfo contains:
/// ```
/// struct TickInfo {
///     uint128 liquidityGross;  // Total liquidity referencing this tick
///     int128 liquidityNet;     // Net liquidity change when crossing this tick
///     ...
/// }
/// ```
///
/// The liquidity data is packed in a single B256 (32 bytes):
/// - liquidity_gross: bits 128-255 (last 16 bytes, u128) - bytes 16-31
/// - liquidity_net: bits 0-127 (first 16 bytes, i128, signed) - bytes 0-15
///
/// This is the first slot of TickInfo data retrieved from State.ticks[tick].
///
/// ## Arguments
/// * `word` - B256 containing packed liquidity_gross and liquidity_net
///
/// ## Returns
/// A tuple of (liquidity_gross, liquidity_net)
pub(crate) fn decode_liquidity_gross_and_net(word: B256) -> (u128, i128) {
    // liquidity_gross is in the second half (bytes 16-31)
    let liquidity_gross = unsafe {
        let ptr = word.0.as_ptr().add(16) as *const u128;
        u128::from_be(ptr.read_unaligned())
    };

    // liquidity_net is in the first half (bytes 0-15), signed
    let liquidity_net = unsafe {
        let ptr = word.0.as_ptr() as *const i128;
        i128::from_be(ptr.read_unaligned())
    };

    (liquidity_gross, liquidity_net)
}

/// Decode tick info from raw B256 data
///
/// This decodes the TickInfo struct from State.ticks mapping:
/// ```
/// struct State {
///     ...
///     mapping(int24 => TickInfo) ticks;  // At state_slot + TICKS_OFFSET
///     ...
/// }
///
/// struct TickInfo {
///     uint128 liquidityGross;                  // data[0] bytes 16-31
///     int128 liquidityNet;                     // data[0] bytes 0-15
///     uint256 feeGrowthOutside0X128;           // data[1]
///     uint256 feeGrowthOutside1X128;           // data[2]
///     ...
/// }
/// ```
///
/// The tick info consists of 3 B256 (32-byte) values retrieved via extsload:
/// - data[0]: Packed liquidity_gross (u128) and liquidity_net (i128)
/// - data[1]: fee_growth_outside0_x128 (U256) - fee growth per unit of
///   liquidity outside this tick for token0
/// - data[2]: fee_growth_outside1_x128 (U256) - fee growth per unit of
///   liquidity outside this tick for token1
///
/// ## Arguments
/// * `data` - Array or slice of 3 B256 values containing tick info data from
///   State.ticks[tick]
///
/// ## Returns
/// A tuple of (liquidity_gross, liquidity_net, fee_growth_outside0_x128,
/// fee_growth_outside1_x128)
pub fn decode_tick_info(data: &[B256]) -> Result<(u128, i128, U256, U256)> {
    if data.len() < 3 {
        return Err(anyhow::anyhow!(
            "Insufficient data: expected 3 B256 values, got {}",
            data.len()
        ));
    }

    let (liquidity_gross, liquidity_net) = decode_liquidity_gross_and_net(data[0]);
    let fee_growth_outside0_x128 = U256::from_be_bytes(data[1].0);
    let fee_growth_outside1_x128 = U256::from_be_bytes(data[2].0);

    Ok((liquidity_gross, liquidity_net, fee_growth_outside0_x128, fee_growth_outside1_x128))
}

/// Decode pool liquidity from raw B256 storage data
///
/// This decodes the liquidity field from the State struct:
/// ```
/// struct State {
///     ...
///     uint128 liquidity;  // At state_slot + LIQUIDITY_OFFSET (state_slot + 3)
///     ...
/// }
/// ```
///
/// In V4's storage layout, the liquidity field is stored as a uint128 in the
/// second half of a 32-byte storage slot (bytes 16-31). The first half (bytes
/// 0-15) may contain padding or other data.
///
/// The data retrieved from `get_liquidity_slot()` via `extsload_0` returns a
/// B256, and this function extracts the uint128 liquidity value from bytes
/// 16-31.
///
/// ## Arguments
/// * `data` - B256 storage data from State.liquidity slot
///
/// ## Returns
/// The liquidity value as u128
pub fn decode_liquidity(data: B256) -> u128 {
    // Liquidity is stored as uint128 in the second half of the storage slot (bytes
    // 16-31)
    unsafe {
        let ptr = data.0.as_ptr().add(16) as *const u128;
        u128::from_be(ptr.read_unaligned())
    }
}

pub fn decode_position_state(data: &[B256]) -> Result<(u128, U256, U256)> {
    if data.len() < 3 {
        return Err(anyhow::anyhow!(
            "Insufficient data: expected 3 B256 values, got {}",
            data.len()
        ));
    }

    let liquidity = decode_liquidity(data[0]);
    let fee_growth_inside0_last_x128 = U256::from_be_bytes(data[1].0);
    let fee_growth_inside1_last_x128 = U256::from_be_bytes(data[2].0);

    Ok((liquidity, fee_growth_inside0_last_x128, fee_growth_inside1_last_x128))
}