use alloy_primitives::{address, Address};
use crate::evm::protocol::curve::adapter::CurveVariant;
pub struct ProbingResults {
pub has_gamma: bool,
pub n_coins: usize,
pub has_math: bool,
pub math_version: Option<String>,
pub has_offpeg_fee_multiplier: bool,
pub has_stored_rates: bool,
pub has_version: bool,
pub has_base_pool: bool,
pub has_int128_balances: bool,
pub pool_address: Address,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetectError {
pub message: String,
}
impl std::fmt::Display for DetectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "cannot detect variant: {}", self.message)
}
}
impl std::error::Error for DetectError {}
pub fn detect_variant(probing: &ProbingResults) -> Result<CurveVariant, DetectError> {
if probing.has_gamma {
detect_cryptoswap(probing)
} else {
Ok(detect_stableswap(probing))
}
}
fn detect_cryptoswap(probing: &ProbingResults) -> Result<CurveVariant, DetectError> {
match probing.n_coins {
3 => {
if KNOWN_TRICRYPTO_V1.contains(&probing.pool_address) {
Ok(CurveVariant::TriCryptoV1)
} else {
Ok(CurveVariant::TriCryptoNG)
}
}
2 => {
if probing.has_math {
match probing.math_version.as_deref() {
Some("v2.0.0" | "v2.1.0") => Ok(CurveVariant::TwoCryptoNG),
Some("v0.1.0") => Ok(CurveVariant::TwoCryptoStable),
_ => Ok(CurveVariant::TwoCryptoNG),
}
} else {
Ok(CurveVariant::TwoCryptoV1)
}
}
n => Err(DetectError {
message: format!("CryptoSwap pool with {n} coins — expected 2 or 3"),
}),
}
}
fn detect_stableswap(probing: &ProbingResults) -> CurveVariant {
if probing.has_stored_rates {
return CurveVariant::StableSwapNG;
}
if probing.has_offpeg_fee_multiplier {
return CurveVariant::StableSwapALend;
}
if probing.has_version {
return CurveVariant::StableSwapNG;
}
if probing.has_base_pool {
return CurveVariant::StableSwapMeta;
}
if probing.has_int128_balances {
return CurveVariant::StableSwapV0;
}
let addr = probing.pool_address;
if KNOWN_V0.contains(&addr) {
CurveVariant::StableSwapV0
} else if KNOWN_V1.contains(&addr) {
CurveVariant::StableSwapV1
} else if KNOWN_STETH.contains(&addr) {
CurveVariant::StableSwapSTETH
} else {
CurveVariant::StableSwapV2
}
}
const KNOWN_TRICRYPTO_V1: [Address; 2] = [
address!("D51a44d3FaE010294C616388b506AcdA1bfAAE46"), address!("80466c64868E1ab14a1Ddf27A676C3fcBE638Fe5"), ];
const KNOWN_V0: [Address; 8] = [
address!("A5407eAE9Ba41422680e2e00537571bcC53efBfD"), address!("A2B47E3D5c44877cca798226B7B8118F9BFb7A56"), address!("79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27"), address!("45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51"), address!("52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C"), address!("06364f10B501e868329afBc005b3492902d6C763"), address!("93054188d876f558f4a66B2EF1d97d16eDf0895B"), address!("7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714"), ];
const KNOWN_V1: [Address; 2] = [
address!("bEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"), address!("4CA9b3063Ec5866A4B82E437059D2C43d1be596F"), ];
const KNOWN_STETH: [Address; 1] = [
address!("DC24316b9AE028F1497c275EB9192a3Ea0f67022"), ];
const NON_ETH_TWOCRYPTO_V1: [Address; 0] = [];
pub fn detect_eth_variant(pool: Address) -> bool {
!NON_ETH_TWOCRYPTO_V1.contains(&pool)
}
#[cfg(test)]
mod tests {
use super::*;
fn addr(s: &str) -> Address {
s.parse().unwrap()
}
#[test]
fn detect_tricrypto_v1_by_address() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 3,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TriCryptoV1);
}
#[test]
fn detect_tricrypto_ng_unknown_3coin() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 3,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TriCryptoNG);
}
#[test]
fn detect_twocrypto_ng_v200() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 2,
has_math: true,
math_version: Some("v2.0.0".to_string()),
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xfb8b95Fb2296a0Ad4b6b1419fdAA5AA5F13e4009"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoNG);
}
#[test]
fn detect_twocrypto_ng_v210() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 2,
has_math: true,
math_version: Some("v2.1.0".to_string()),
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: Address::ZERO,
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoNG);
}
#[test]
fn detect_twocrypto_stable_v010() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 2,
has_math: true,
math_version: Some("v0.1.0".to_string()),
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0x6e5492F8ea2370844EE098A56DD88e1717e4A9C2"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoStable);
}
#[test]
fn detect_twocrypto_v1_no_math() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 2,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoV1);
}
#[test]
fn detect_twocrypto_ng_unknown_math_version() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 2,
has_math: true,
math_version: Some("v3.0.0".to_string()),
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: Address::ZERO,
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoNG);
}
#[test]
fn detect_crypto_unsupported_coin_count() {
let probing = ProbingResults {
has_gamma: true,
n_coins: 4,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: Address::ZERO,
};
assert!(detect_variant(&probing).is_err());
}
#[test]
fn detect_stableswap_ng() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 2,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: true,
has_stored_rates: true,
has_version: true,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xF36a4BA50C603204c3FC6d2dA8b78A7b69CBC67d"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapNG);
}
#[test]
fn detect_stableswap_ng_without_offpeg() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 2,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: true,
has_version: true,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0x1539c2461d7432cc114b0903f1824079BfCA2C92"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapNG);
}
#[test]
fn detect_stableswap_ng_via_version() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 3,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: true,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapNG);
}
#[test]
fn detect_stableswap_alend() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 3,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: true,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xDeBF20617708857ebe4F679508E7b7863a8A8EeE"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapALend);
}
#[test]
fn detect_stableswap_meta() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 2,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: true,
has_int128_balances: false,
pool_address: addr("0x4f062658EaAF2C1ccf8C8e36D6824CDf41167956"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapMeta);
}
#[test]
fn detect_stableswap_v0_by_int128() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 4,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: true,
pool_address: addr("0xA5407eAE9Ba41422680e2e00537571bcC53efBfD"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV0);
}
#[test]
fn detect_stableswap_v0_by_known_address() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 4,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xA5407eAE9Ba41422680e2e00537571bcC53efBfD"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV0);
}
#[test]
fn detect_stableswap_v1_3pool() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 3,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV1);
}
#[test]
fn detect_stableswap_steth_by_known_address() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 2,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapSTETH);
}
#[test]
fn detect_eth_variant_defaults_true() {
assert!(detect_eth_variant(addr("0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511")));
assert!(detect_eth_variant(Address::ZERO));
}
#[test]
fn detect_stableswap_v2_default() {
let probing = ProbingResults {
has_gamma: false,
n_coins: 2,
has_math: false,
math_version: None,
has_offpeg_fee_multiplier: false,
has_stored_rates: false,
has_version: false,
has_base_pool: false,
has_int128_balances: false,
pool_address: addr("0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2"),
};
assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV2);
}
}