wp-evm-algebra-interfaces 0.1.10

sol!-generated bindings for Algebra V1.9 protocol interfaces
Documentation
//! Algebra V1.9 pool mutable-state reads.

use alloy_sol_types::sol;

sol! {
    interface IAlgebraPoolState {
        function globalState() external view returns (
            uint160 price,
            int24 tick,
            uint16 fee,
            uint16 timepointIndex,
            uint8 communityFeeToken0,
            uint8 communityFeeToken1,
            bool unlocked
        );

        function liquidity() external view returns (uint128);
        /// Algebra V1 pool's cumulative LP fee accumulator for token0,
        /// in `X128` fixed-point. NOTE the name is `totalFeeGrowth0Token`
        /// in Algebra V1's `AlgebraPool.sol`, not `feeGrowthGlobal0X128`
        /// like Uniswap V3 — different naming, same semantics (LP-net
        /// fee growth, protocol cut already deducted). Verified
        /// against canonical QuickSwap pool 0x7b92…a710 on Polygon —
        /// `totalFeeGrowth0Token()` returns real state, the
        /// Uniswap-named alternative reverts.
        function totalFeeGrowth0Token() external view returns (uint256);
        function totalFeeGrowth1Token() external view returns (uint256);
        function ticks(int24 tick) external view returns (
            uint128 liquidityTotal,
            int128 liquidityDelta,
            uint256 outerFeeGrowth0Token,
            uint256 outerFeeGrowth1Token,
            int56 outerTickCumulative,
            uint160 outerSecondsPerLiquidity,
            uint32 outerSecondsSpent,
            bool initialized
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloy_sol_types::SolCall;

    #[test]
    fn selectors_are_locked() {
        assert_eq!(IAlgebraPoolState::globalStateCall::SELECTOR, [0xe7, 0x6c, 0x01, 0xe4]);
        assert_eq!(IAlgebraPoolState::liquidityCall::SELECTOR, [0x1a, 0x68, 0x65, 0x02]);
        // Algebra V1 names — verified on-chain against QuickSwap pool
        // 0x7b92…a710 on Polygon (both at FORK_BLOCK 86_077_558 and
        // latest). Wrong Uniswap-style names would revert here AND
        // miss this lock; the on-chain decode test in
        // `wp-evm-algebra-provider`'s fork-parity suite is the only
        // safety net for catching ABI drift downstream.
        assert_eq!(IAlgebraPoolState::totalFeeGrowth0TokenCall::SELECTOR, [0x63, 0x78, 0xae, 0x44]);
        assert_eq!(IAlgebraPoolState::totalFeeGrowth1TokenCall::SELECTOR, [0xec, 0xde, 0xcf, 0x42]);
        assert_eq!(IAlgebraPoolState::ticksCall::SELECTOR, [0xf3, 0x0d, 0xba, 0x93]);
    }

    /// Lock the `IAlgebraPoolState::totalFeeGrowth0Token` decode against
    /// real-world bytes from the canonical QuickSwap V3 USDC.e/USDT
    /// pool (`0x7b925e617aefd7fb3a93abe3a701135d7a1ba710`) on Polygon at
    /// block `86_077_558` (`0x520F046`).
    ///
    /// **Why this test exists (audit Slice 2 — plan PR #225):** the
    /// keccak-only selector lock above is tautological — both sides
    /// derive from the same declared name, so an interface that
    /// declared `feeGrowthGlobal0Token` (Uniswap-style) would still
    /// match its own selector even though the deployed Algebra pool
    /// only exposes `totalFeeGrowth0Token` (PR #224, the bug this
    /// audit was provoked by). This real-bytes lock proves the
    /// declared name actually exists on the on-chain ABI and that the
    /// uint256 return decodes — so a future "consistency-rename" back
    /// to the V3 name fails loudly here, not silently in production.
    ///
    /// Captured 2026-05-28 via:
    ///   cast rpc eth_call --rpc-url <POLYGON_MAINNET_RPC> \
    ///     '{"to":"0x7b925e617aefd7fb3a93abe3a701135d7a1ba710",\
    ///       "data":"0x6378ae44"}' \
    ///     0x520F046
    #[test]
    #[allow(non_snake_case)]
    fn real_quickswap_totalFeeGrowth0Token_decodes() {
        use alloy_primitives::U256;

        let raw = alloy_primitives::hex!(
            "00000000000000000000000000000000003c54f30345461bbcf7625c69498981" // totalFeeGrowth0Token
        );
        let decoded = IAlgebraPoolState::totalFeeGrowth0TokenCall::abi_decode_returns(&raw)
            .expect("decode uint256");

        assert_eq!(
            decoded,
            U256::from_str_radix("313260787374489352611272212014008705", 10).unwrap(),
        );
    }

    /// Lock the `IAlgebraPoolState::totalFeeGrowth1Token` decode against
    /// real-world bytes from the canonical QuickSwap V3 USDC.e/USDT
    /// pool on Polygon at block `86_077_558` (`0x520F046`).
    ///
    /// Companion to `real_quickswap_totalFeeGrowth0Token_decodes`. Same
    /// rationale: the `totalFeeGrowth*Token` name pair is the exact
    /// surface fixed by PR #224 — silently mis-naming it back to
    /// Uniswap V3's `feeGrowthGlobal*X128` would pass the selector
    /// lock but revert on-chain. Locking against a real captured
    /// return prevents the regression.
    ///
    /// Captured 2026-05-28 via:
    ///   cast rpc eth_call --rpc-url <POLYGON_MAINNET_RPC> \
    ///     '{"to":"0x7b925e617aefd7fb3a93abe3a701135d7a1ba710",\
    ///       "data":"0xecdecf42"}' \
    ///     0x520F046
    #[test]
    #[allow(non_snake_case)]
    fn real_quickswap_totalFeeGrowth1Token_decodes() {
        use alloy_primitives::U256;

        let raw = alloy_primitives::hex!(
            "0000000000000000000000000000000000452c6a1861f1ce2b262918b7a43f2f" // totalFeeGrowth1Token
        );
        let decoded = IAlgebraPoolState::totalFeeGrowth1TokenCall::abi_decode_returns(&raw)
            .expect("decode uint256");

        assert_eq!(
            decoded,
            U256::from_str_radix("359169314992738225562812068319739695", 10).unwrap(),
        );
    }

    /// Lock the `IAlgebraPoolState::liquidity` decode against real-world
    /// bytes from the canonical QuickSwap V3 USDC.e/USDT pool on Polygon
    /// at block `86_077_558` (`0x520F046`).
    ///
    /// `liquidity()` returns a single `uint128` (in-range active
    /// liquidity). Same selector and shape as V3 — the real-bytes lock
    /// is the second layer of defense (alongside the selector lock)
    /// that proves the declared name decodes against the deployed
    /// Algebra pool.
    ///
    /// Captured 2026-05-28 via:
    ///   cast rpc eth_call --rpc-url <POLYGON_MAINNET_RPC> \
    ///     '{"to":"0x7b925e617aefd7fb3a93abe3a701135d7a1ba710",\
    ///       "data":"0x1a686502"}' \
    ///     0x520F046
    #[test]
    fn real_quickswap_liquidity_decodes() {
        let raw = alloy_primitives::hex!(
            "000000000000000000000000000000000000000000000000000008d714295a71" // liquidity (u128)
        );
        let decoded =
            IAlgebraPoolState::liquidityCall::abi_decode_returns(&raw).expect("decode uint128");

        assert_eq!(decoded, 9_719_849_245_297u128);
    }

    /// Lock the 7-tuple decode of `IAlgebraPoolState::globalStateCall`
    /// against real-world bytes from the canonical QuickSwap V3
    /// USDC.e/USDT pool (`0x7b925e617aefd7fb3a93abe3a701135d7a1ba710`)
    /// on Polygon at block `86_077_558` (`0x520F046`).
    ///
    /// **Algebra-vs-V3 nuance:** Algebra's `globalState()` returns a
    /// 7-tuple but with **different field names AND a different type
    /// at slot 4** than V3's `slot0()`:
    /// `(price, tick, fee, timepointIndex, communityFeeToken0,
    /// communityFeeToken1, unlocked)` — NOT V3's
    /// `(sqrtPriceX96, tick, observationIndex, observationCardinality,
    /// observationCardinalityNext, feeProtocol, unlocked)`. Semantics
    /// at slots 2/3/4/5 diverge; slot 4 is `u8 communityFeeToken0` in
    /// Algebra vs `u16 observationCardinalityNext` in V3 (both encode
    /// to a 32-byte ABI word, so a wrong type wouldn't change the
    /// captured bytes — only the `assert_eq!` type would mismatch).
    /// Anyone auto-completing the field names from V3 would silently
    /// break this test. The tuple **length** (7) happens to match.
    ///
    /// At this block: dynamic-fee Algebra pool reports a `fee` of
    /// `10` bps and `communityFeeToken0 = communityFeeToken1 = 150`
    /// (15% community-fee share, the QuickSwap V1 default), with the
    /// pool idle (`unlocked = true`).
    ///
    /// Captured 2026-05-28 via:
    ///   cast rpc eth_call --rpc-url <POLYGON_MAINNET_RPC> \
    ///     '{"to":"0x7b925e617aefd7fb3a93abe3a701135d7a1ba710",\
    ///       "data":"0xe76c01e4"}' \
    ///     0x520F046
    #[test]
    #[allow(non_snake_case)]
    fn real_quickswap_globalState_decodes_7_tuple() {
        use alloy_primitives::{aliases::U160, U256};
        use IAlgebraPoolState::globalStateReturn;

        let raw = alloy_primitives::hex!(
            "0000000000000000000000000000000000000000fff41240e6d6248c64e0a1d2" // price (u160)
            "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc" // tick (i24, -4)
            "000000000000000000000000000000000000000000000000000000000000000a" // fee (u16)
            "0000000000000000000000000000000000000000000000000000000000001ad2" // timepointIndex (u16)
            "0000000000000000000000000000000000000000000000000000000000000096" // communityFeeToken0 (u8)
            "0000000000000000000000000000000000000000000000000000000000000096" // communityFeeToken1 (u8)
            "0000000000000000000000000000000000000000000000000000000000000001" // unlocked (bool)
        );
        let decoded: globalStateReturn =
            IAlgebraPoolState::globalStateCall::abi_decode_returns(&raw).expect("decode 7-tuple");

        // Lock every field — exact values pinned at Polygon block 86_077_558.
        assert_eq!(
            decoded.price,
            U160::from(U256::from_str_radix("79213741604250796873618596306", 10).unwrap()),
        );
        assert_eq!(decoded.tick.as_i32(), -4);
        assert_eq!(decoded.fee, 10u16);
        assert_eq!(decoded.timepointIndex, 6_866u16);
        // 150 = 0x96 — QuickSwap V1 default community-fee share (15%).
        assert_eq!(decoded.communityFeeToken0, 150u8);
        assert_eq!(decoded.communityFeeToken1, 150u8);
        assert!(decoded.unlocked);
    }

    /// Lock the 8-tuple decode of `IAlgebraPoolState::ticksCall` against
    /// real-world bytes from the canonical QuickSwap V3 USDC.e/USDT
    /// pool at tick `-180` (the `tickLower` of position 1962 — the
    /// same fixture used by the existing fork-parity test and by the
    /// `real_quickswap_positions_return_decodes_11_tuple` NFPM test).
    /// Captured at Polygon block `86_077_558` (`0x520F046`).
    ///
    /// **Algebra-vs-V3 nuance:** Algebra's `ticks(int24)` 8-tuple uses
    /// `outer*` naming (Algebra V1 source idiom), NOT V3's `Outside*`
    /// naming. The field list is
    /// `(liquidityTotal, liquidityDelta, outerFeeGrowth0Token,
    /// outerFeeGrowth1Token, outerTickCumulative,
    /// outerSecondsPerLiquidity, outerSecondsSpent, initialized)` —
    /// NOT V3's
    /// `(liquidityGross, liquidityNet, feeGrowthOutside0X128,
    /// feeGrowthOutside1X128, tickCumulativeOutside,
    /// secondsPerLiquidityOutsideX128, secondsOutside, initialized)`.
    /// A V3-shaped auto-completion would silently fail.
    ///
    /// Tick `-180` is initialized at this block (it's the lower bound
    /// of the active LP position 1962). At this fixture
    /// `liquidityTotal == liquidityDelta` — both numerical values are
    /// locked below directly from the captured bytes; the standalone
    /// equality is a property of this particular boundary state and
    /// is not asserted as a structural invariant.
    ///
    /// Captured 2026-05-28 via:
    ///   cast rpc eth_call --rpc-url <POLYGON_MAINNET_RPC> \
    ///     '{"to":"0x7b925e617aefd7fb3a93abe3a701135d7a1ba710",\
    ///       "data":"0xf30dba93ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4c"}' \
    ///     0x520F046
    #[test]
    fn real_quickswap_ticks_return_decodes_8_tuple() {
        use alloy_primitives::{
            aliases::{I56, U160},
            U256,
        };
        use IAlgebraPoolState::ticksReturn;

        let raw = alloy_primitives::hex!(
            "0000000000000000000000000000000000000000000000000000015e572f2183" // liquidityTotal (u128)
            "0000000000000000000000000000000000000000000000000000015e572f2183" // liquidityDelta (i128)
            "00000000000000000000000000000000002ab7f8589d59b52a8748390aed29dd" // outerFeeGrowth0Token (u256)
            "000000000000000000000000000000000031c0cb10bc54384ce178a5d061dad3" // outerFeeGrowth1Token (u256)
            "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffa336de3" // outerTickCumulative (i56)
            "00000000000000000000000000000000000bd50522e3bfebaac7ce0555dd7bd6" // outerSecondsPerLiquidity (u160)
            "00000000000000000000000000000000000000000000000000000000632b3041" // outerSecondsSpent (u32)
            "0000000000000000000000000000000000000000000000000000000000000001" // initialized (bool)
        );
        let decoded: ticksReturn =
            IAlgebraPoolState::ticksCall::abi_decode_returns(&raw).expect("decode 8-tuple");

        // Lock every field — exact values pinned at Polygon block 86_077_558.
        assert_eq!(decoded.liquidityTotal, 1_504_701_260_163u128);
        assert_eq!(decoded.liquidityDelta, 1_504_701_260_163i128);
        assert_eq!(
            decoded.outerFeeGrowth0Token,
            U256::from_str_radix("221807825025140404141599441057294813", 10).unwrap(),
        );
        assert_eq!(
            decoded.outerFeeGrowth1Token,
            U256::from_str_radix("258332857208533998751065914378148563", 10).unwrap(),
        );
        assert_eq!(decoded.outerTickCumulative, I56::try_from(-97_292_829i64).unwrap());
        assert_eq!(
            decoded.outerSecondsPerLiquidity,
            U160::from_str_radix("61435825628096295303193804270304214", 10).unwrap(),
        );
        assert_eq!(decoded.outerSecondsSpent, 1_663_774_785u32);
        assert!(decoded.initialized);
    }
}