wp-evm-algebra-core 0.1.14

Pure data + quote + plan for Algebra-family CL DEXes
Documentation
//! Pure CREATE2 pool address derivation for AlgebraV1.9-shaped DEXes.
//!
//! Mirrors AlgebraV1's `PoolAddress.computeAddress`:
//!
//! ```solidity
//! pool = address(uint256(keccak256(abi.encodePacked(
//!     hex'ff', factory,
//!     keccak256(abi.encode(key.token0, key.token1)),
//!     POOL_INIT_CODE_HASH
//! ))));
//! ```
//!
//! Differences from V3-family derivation (`wp-evm-v3-core::pool_address`):
//!
//! - **No fee in salt.** Algebra pools are keyed by `(token0, token1)`
//!   only — fee is dynamic and lives in `globalState()`.
//! - **`pool_deployer`, not `factory`, is the CREATE2 caller.** Algebra
//!   has an independent `AlgebraPoolDeployer`. The factory is only used
//!   for `poolByPair` lookups.

use alloy_primitives::{keccak256, Address, B256};
use alloy_sol_types::SolValue;

/// Derive the Algebra pool address for `(token_a, token_b)` under the
/// given CREATE2 deployer and pool init-code hash. Tokens may be passed
/// in any order — the function sorts them internally so `token0 < token1`.
///
/// Pure: no I/O, no allocation beyond the 64-byte salt preimage.
pub fn compute(
    deployer: Address,
    init_code_hash: B256,
    token_a: Address,
    token_b: Address,
) -> Address {
    debug_assert!(token_a != token_b, "pool_address::compute requires distinct tokens");

    let (token0, token1) = if token_a < token_b { (token_a, token_b) } else { (token_b, token_a) };

    let salt = keccak256((token0, token1).abi_encode());
    deployer.create2(salt, init_code_hash)
}

/// CREATE2 pool address for Algebra forks with the custom-pool-deployer feature.
/// `custom_deployer` is the pool's `deployer` field (Address::ZERO for the default
/// deployer). Salt = keccak256(abi.encode(t0,t1)) when custom_deployer is zero, else
/// keccak256(abi.encode(custom_deployer, t0, t1)).
pub fn compute_with_custom_deployer(
    pool_deployer: Address,
    init_code_hash: B256,
    custom_deployer: Address,
    token_a: Address,
    token_b: Address,
) -> Address {
    let (token0, token1) = if token_a < token_b { (token_a, token_b) } else { (token_b, token_a) };

    let salt_data = if custom_deployer == Address::ZERO {
        (token0, token1).abi_encode()
    } else {
        (custom_deployer, token0, token1).abi_encode()
    };
    let salt_hash = keccak256(salt_data);

    let mut input = Vec::with_capacity(1 + 20 + 32 + 32);
    input.push(0xff);
    input.extend_from_slice(pool_deployer.as_slice());
    input.extend_from_slice(salt_hash.as_slice());
    input.extend_from_slice(init_code_hash.as_slice());

    Address::from_slice(&keccak256(input).as_slice()[12..])
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::data::AlgebraProtocolConfig;
    use alloy_primitives::{address, b256};

    /// Canonical QuickSwap V3 (Algebra V1) Polygon mainnet config.
    /// Kept local so this test does not depend on the facade crate
    /// (`wp-evm-quickswap`), avoiding a back-reference.
    const QUICKSWAP_POLYGON: AlgebraProtocolConfig = AlgebraProtocolConfig {
        factory: address!("411b0fAcC3489691f28ad58c47006AF5E3Ab3A28"),
        pool_deployer: address!("2D98E2FA9da15aa6dC9581AB097Ced7af697CB92"),
        router: address!("f5b509bB0909a69B1c207E495f687a596C168E12"),
        position_mgr: address!("8eF88E4c7CfbbaC1C163f7eddd4B578792201de6"),
        init_code_hash: b256!("6ec6c9c8091d160c0aa74b2b14ba9c1717e95093bd3ac085cee99a49aab294a4"),
        multicall: address!("cA11bde05977b3631167028862bE2a173976CA11"),
        quoter: None,
        farming_center: None,
    };

    /// USDC.e Polygon
    const USDC_E: Address = address!("2791Bca1f2de4661ED88A30C99A7a9449Aa84174");
    /// WETH Polygon
    const WETH: Address = address!("7ceB23fD6bC0adD59E62ac25578270cFf1b9f619");
    /// USDC.e / WETH pool on QuickSwap V3 Polygon — confirmed via
    /// AlgebraFactory.poolByPair RPC and end-to-end CREATE2 reproduction.
    /// Source: <https://polygonscan.com/address/0x55CAaBB0d2b704FD0eF8192A7E35D8837e678207>
    const USDC_E_WETH_POOL: Address = address!("55CAaBB0d2b704FD0eF8192A7E35D8837e678207");

    #[test]
    fn canonical_quickswap_polygon_usdce_weth_matches_pool() {
        let pool = compute(
            QUICKSWAP_POLYGON.pool_deployer,
            QUICKSWAP_POLYGON.init_code_hash,
            USDC_E,
            WETH,
        );
        assert_eq!(pool, USDC_E_WETH_POOL);
    }

    #[test]
    fn custom_deployer_create2_matches_blackhole_fixture() {
        use alloy_primitives::{address, b256};
        let pool = compute_with_custom_deployer(
            address!("9B2441037E286d5Bf9456a3BE7b5273fe28DbA1e"),
            b256!("eaa3eea3233916c82fe1281a51bd9cde844b7c4673c0714ca0028a57f5634752"),
            address!("5D433A94A4a2aA8f9AA34D8D15692Dc2E9960584"),
            address!("49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab"),
            address!("b31f66aa3c1e785363f0875a1b74e27b85fd66c7"),
        );
        assert_eq!(pool, address!("5e128ebc09c918ddae3ca1668d4ee9527dc00d78"));
    }

    #[test]
    fn token_order_does_not_change_result() {
        let forward = compute(
            QUICKSWAP_POLYGON.pool_deployer,
            QUICKSWAP_POLYGON.init_code_hash,
            USDC_E,
            WETH,
        );
        let reversed = compute(
            QUICKSWAP_POLYGON.pool_deployer,
            QUICKSWAP_POLYGON.init_code_hash,
            WETH,
            USDC_E,
        );
        assert_eq!(forward, reversed);
        assert_eq!(forward, USDC_E_WETH_POOL);
    }

    #[test]
    #[cfg(debug_assertions)]
    #[should_panic(expected = "distinct tokens")]
    fn identical_tokens_panics_in_debug() {
        let _ = compute(
            QUICKSWAP_POLYGON.pool_deployer,
            QUICKSWAP_POLYGON.init_code_hash,
            USDC_E,
            USDC_E,
        );
    }
}