wp-evm-algebra-core 0.1.14

Pure data + quote + plan for Algebra-family CL DEXes
Documentation
//! Pure quote functions for the Algebra family (V1.9 variant).
//!
//! # Delegation to wp-evm-v3-core
//!
//! Algebra forks Uniswap V3's concentrated-liquidity pool core, so the
//! underlying swap math is identical. Rather than duplicating ~200 LoC of
//! SDK wiring, this module converts an Algebra `PoolState` into a
//! `wp_evm_v3_core::data::PoolState` and delegates to
//! `wp_evm_v3_core::quote::exact_in_with_fee_fn` /
//! `exact_out_with_fee_fn`.
//!
//! The only algebra-specific behaviour here is that the fee is always
//! taken from `state.fee` (the dynamic fee read from `globalState()`)
//! — there is no static fee tier to override.
//!
//! If the wp-evm-v3-core SDK math diverges from Algebra's pool math in the
//! future, replace the delegation with a local implementation.

use crate::data::{ExactInParams, ExactOutParams, PoolState, Quote};

pub use wp_evm_v3_core::quote::QuoteError;

/// Convert an Algebra `PoolState` to the structurally identical
/// `wp_evm_v3_core::data::PoolState` so we can delegate to wp-evm-v3-core's
/// swap math.
fn as_v3_pool_state(state: &PoolState) -> wp_evm_v3_core::data::PoolState {
    wp_evm_v3_core::data::PoolState {
        token0: state.token0,
        token1: state.token1,
        fee: state.fee,
        tick_spacing: state.tick_spacing,
        sqrt_price_x96: state.sqrt_price_x96,
        liquidity: state.liquidity,
        tick: state.tick,
        ticks: state
            .ticks
            .iter()
            .map(|t| wp_evm_v3_core::data::TickInfo {
                tick: t.tick,
                liquidity_net: t.liquidity_net,
                liquidity_gross: t.liquidity_gross,
            })
            .collect(),
    }
}

fn as_v3_exact_in(params: &ExactInParams) -> wp_evm_v3_core::data::ExactInParams {
    wp_evm_v3_core::data::ExactInParams {
        token_in: params.token_in,
        token_out: params.token_out,
        amount_in: params.amount_in,
        recipient: params.recipient,
    }
}

fn as_v3_exact_out(params: &ExactOutParams) -> wp_evm_v3_core::data::ExactOutParams {
    wp_evm_v3_core::data::ExactOutParams {
        token_in: params.token_in,
        token_out: params.token_out,
        amount_out: params.amount_out,
        recipient: params.recipient,
    }
}

/// Quote an exact-input swap against an AlgebraV1.9 pool.
///
/// The current dynamic fee from `state.fee` is injected into the wp-evm-v3-core
/// SDK pool. No static fee tiers exist in Algebra — fee is always dynamic.
pub fn exact_in(state: &PoolState, params: &ExactInParams) -> Result<Quote, QuoteError> {
    let v3_state = as_v3_pool_state(state);
    let v3_params = as_v3_exact_in(params);
    let fee = state.fee;
    let v3_quote =
        wp_evm_v3_core::quote::exact_in_with_fee_fn(&v3_state, &v3_params, move |_| fee)?;
    Ok(Quote {
        amount_in: v3_quote.amount_in,
        amount_out: v3_quote.amount_out,
        sqrt_price_x96_after: v3_quote.sqrt_price_x96_after,
        price_impact_bps: v3_quote.price_impact_bps,
        effective_fee_pips: state.fee,
    })
}

/// Quote an exact-output swap against an AlgebraV1.9 pool.
pub fn exact_out(state: &PoolState, params: &ExactOutParams) -> Result<Quote, QuoteError> {
    let v3_state = as_v3_pool_state(state);
    let v3_params = as_v3_exact_out(params);
    let fee = state.fee;
    let v3_quote =
        wp_evm_v3_core::quote::exact_out_with_fee_fn(&v3_state, &v3_params, move |_| fee)?;
    Ok(Quote {
        amount_in: v3_quote.amount_in,
        amount_out: v3_quote.amount_out,
        sqrt_price_x96_after: v3_quote.sqrt_price_x96_after,
        price_impact_bps: v3_quote.price_impact_bps,
        effective_fee_pips: state.fee,
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::data::{ExactInParams, ExactOutParams, PoolState, TickInfo};
    use alloy_primitives::{address, U256};

    /// Fixture: USDC/WETH pool with a dynamic 500-pip fee (0.05%).
    ///
    /// Same pool geometry as the wp-evm-v3-core fixture (tick 76012, two
    /// overlapping ranges) but the fee comes from Algebra's `globalState()`
    /// rather than a static fee tier.
    fn fixture_algebra_usdc_weth() -> PoolState {
        let sqrt_price_x96: U256 =
            U256::from_str_radix("3543191142285914205922034323214", 10).unwrap();
        PoolState {
            token0: address!("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
            token1: address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
            fee: 500, // dynamic fee from globalState()
            tick_spacing: 60,
            sqrt_price_x96,
            liquidity: 2_000_000_000_000_000_000_000u128,
            tick: 76012,
            ticks: vec![
                TickInfo {
                    tick: 74940,
                    liquidity_net: 1_000_000_000_000_000_000_000i128,
                    liquidity_gross: 1_000_000_000_000_000_000_000u128,
                },
                TickInfo {
                    tick: 75960,
                    liquidity_net: 1_000_000_000_000_000_000_000i128,
                    liquidity_gross: 1_000_000_000_000_000_000_000u128,
                },
                TickInfo {
                    tick: 76020,
                    liquidity_net: -2_000_000_000_000_000_000_000i128,
                    liquidity_gross: 2_000_000_000_000_000_000_000u128,
                },
            ],
        }
    }

    #[test]
    fn exact_in_returns_nonzero_amount_out() {
        let s = fixture_algebra_usdc_weth();
        let p = ExactInParams {
            token_in: s.token0,
            token_out: s.token1,
            amount_in: U256::from(1_000_000u64),
            recipient: address!("0x0000000000000000000000000000000000000099"),
        };
        let q = exact_in(&s, &p).expect("quote ok");
        assert!(q.amount_out > U256::ZERO);
        assert_eq!(q.amount_in, p.amount_in);
        assert_eq!(q.effective_fee_pips, 500);
    }

    #[test]
    fn exact_out_round_trip_within_tolerance() {
        let s = fixture_algebra_usdc_weth();
        let p_in = ExactInParams {
            token_in: s.token0,
            token_out: s.token1,
            amount_in: U256::from(1_000_000u64),
            recipient: address!("0x0000000000000000000000000000000000000099"),
        };
        let q_in = exact_in(&s, &p_in).unwrap();

        let p_out = ExactOutParams {
            token_in: s.token0,
            token_out: s.token1,
            amount_out: q_in.amount_out,
            recipient: p_in.recipient,
        };
        let q_out = exact_out(&s, &p_out).unwrap();

        let diff = if q_out.amount_in > p_in.amount_in {
            q_out.amount_in - p_in.amount_in
        } else {
            p_in.amount_in - q_out.amount_in
        };
        assert!(diff <= U256::from(1_000u64), "round-trip diff = {}", diff);
    }

    #[test]
    fn exact_in_rejects_unknown_token() {
        let s = fixture_algebra_usdc_weth();
        let bogus = address!("0x000000000000000000000000000000000000dead");
        let p = ExactInParams {
            token_in: bogus,
            token_out: s.token1,
            amount_in: U256::from(1_000_000u64),
            recipient: address!("0x0000000000000000000000000000000000000099"),
        };
        let err = exact_in(&s, &p).expect_err("should reject unknown token");
        assert!(matches!(err, QuoteError::UnknownToken));
    }

    #[test]
    fn effective_fee_pips_propagates_from_state() {
        // Verify that the Quote.effective_fee_pips always reflects state.fee
        // (the dynamic fee), not any hardcoded value.
        let mut s = fixture_algebra_usdc_weth();
        s.fee = 1500; // simulate a higher dynamic fee
        let p = ExactInParams {
            token_in: s.token0,
            token_out: s.token1,
            amount_in: U256::from(1_000u64),
            recipient: address!("0x0000000000000000000000000000000000000099"),
        };
        let q = exact_in(&s, &p).expect("quote ok");
        assert_eq!(q.effective_fee_pips, 1500);
    }
}