use alloy_primitives::{keccak256, Address, B256, U256};
use alloy_sol_types::SolValue;
pub fn compute(
deployer: Address,
init_code_hash: B256,
token_a: Address,
token_b: Address,
fee: u32,
) -> Address {
debug_assert!(token_a != token_b, "pool_address::compute requires distinct tokens");
debug_assert!(fee < (1u32 << 24), "V3 fee must fit in uint24");
let (token0, token1) = if token_a < token_b { (token_a, token_b) } else { (token_b, token_a) };
let salt = keccak256((token0, token1, U256::from(fee)).abi_encode());
deployer.create2(salt, init_code_hash)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::{SwapRouterKind, V3ProtocolConfig};
use alloy_primitives::{address, b256};
const UNISWAP_V3_MAINNET: V3ProtocolConfig = V3ProtocolConfig {
factory: address!("1F98431c8aD98523631AE4a59f267346ea31F984"),
pool_deployer: None,
router: address!("E592427A0AEce92De3Edee1F18E0157C05861564"),
swap_router_kind: SwapRouterKind::V1,
position_mgr: address!("C36442b4a4522E871399CD717aBDD847Ab11FE88"),
init_code_hash: b256!("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"),
fee_tiers: &[100, 500, 3000, 10000],
multicall: address!("cA11bde05977b3631167028862bE2a173976CA11"),
quoter: None,
};
const USDC: Address = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
const WETH: Address = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
const USDC_WETH_500_POOL: Address = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640");
#[test]
fn canonical_uniswap_v3_usdc_weth_500_matches_mainnet() {
let pool = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
500,
);
assert_eq!(pool, USDC_WETH_500_POOL);
}
#[test]
fn token_order_does_not_change_result() {
let forward = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
500,
);
let reversed = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
WETH,
USDC,
500,
);
assert_eq!(forward, reversed);
assert_eq!(forward, USDC_WETH_500_POOL);
}
#[test]
fn different_fee_tier_yields_different_address() {
let p500 = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
500,
);
let p3000 = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
3000,
);
assert_ne!(p500, p3000);
}
#[test]
fn fee_zero_produces_deterministic_nonzero_address() {
let pool = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
0,
);
assert_ne!(pool, Address::ZERO);
assert_eq!(
pool,
compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
0,
)
);
}
#[test]
fn fee_max_uint24_produces_deterministic_nonzero_address() {
let max_uint24 = (1u32 << 24) - 1;
let pool = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
max_uint24,
);
assert_ne!(pool, Address::ZERO);
assert_eq!(
pool,
compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
WETH,
max_uint24,
)
);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "distinct tokens")]
fn identical_tokens_panics_in_debug() {
let _ = compute(
UNISWAP_V3_MAINNET.pool_deployer.unwrap_or(UNISWAP_V3_MAINNET.factory),
UNISWAP_V3_MAINNET.init_code_hash,
USDC,
USDC,
500,
);
}
#[test]
fn compute_with_pancake_deployer_matches_real_pool() {
let pancake_factory: Address = address!("0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865");
let pancake_deployer: Address = address!("41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9");
let pancake_init_code_hash: B256 =
b256!("6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2");
let usdt: Address = address!("55d398326f99059fF775485246999027B3197955");
let usdc: Address = address!("8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d");
let expected_pool: Address = address!("92b7807bF19b7DDdf89b706143896d05228f3121");
let computed_with_deployer =
compute(pancake_deployer, pancake_init_code_hash, usdt, usdc, 100);
assert_eq!(computed_with_deployer, expected_pool);
let computed_with_factory =
compute(pancake_factory, pancake_init_code_hash, usdt, usdc, 100);
assert_ne!(computed_with_factory, expected_pool);
}
}