1use alloy_primitives::{address, Address};
36
37use crate::evm::protocol::curve::adapter::CurveVariant;
38
39pub struct ProbingResults {
62 pub has_gamma: bool,
64
65 pub n_coins: usize,
67
68 pub has_math: bool,
70
71 pub math_version: Option<String>,
73
74 pub has_offpeg_fee_multiplier: bool,
78
79 pub has_stored_rates: bool,
83
84 pub has_version: bool,
89
90 pub has_base_pool: bool,
93
94 pub has_int128_balances: bool,
96
97 pub pool_address: Address,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct DetectError {
104 pub message: String,
105}
106
107impl std::fmt::Display for DetectError {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 write!(f, "cannot detect variant: {}", self.message)
110 }
111}
112
113impl std::error::Error for DetectError {}
114
115pub fn detect_variant(probing: &ProbingResults) -> Result<CurveVariant, DetectError> {
125 if probing.has_gamma {
126 detect_cryptoswap(probing)
127 } else {
128 Ok(detect_stableswap(probing))
129 }
130}
131
132fn detect_cryptoswap(probing: &ProbingResults) -> Result<CurveVariant, DetectError> {
133 match probing.n_coins {
134 3 => {
135 if KNOWN_TRICRYPTO_V1.contains(&probing.pool_address) {
136 Ok(CurveVariant::TriCryptoV1)
137 } else {
138 Ok(CurveVariant::TriCryptoNG)
139 }
140 }
141 2 => {
142 if probing.has_math {
143 match probing.math_version.as_deref() {
144 Some("v2.0.0" | "v2.1.0") => Ok(CurveVariant::TwoCryptoNG),
145 Some("v0.1.0") => Ok(CurveVariant::TwoCryptoStable),
146 _ => Ok(CurveVariant::TwoCryptoNG),
148 }
149 } else {
150 Ok(CurveVariant::TwoCryptoV1)
152 }
153 }
154 n => Err(DetectError {
155 message: format!("CryptoSwap pool with {n} coins — expected 2 or 3"),
156 }),
157 }
158}
159
160fn detect_stableswap(probing: &ProbingResults) -> CurveVariant {
161 if probing.has_stored_rates {
164 return CurveVariant::StableSwapNG;
165 }
166
167 if probing.has_offpeg_fee_multiplier {
169 return CurveVariant::StableSwapALend;
170 }
171
172 if probing.has_version {
175 return CurveVariant::StableSwapNG;
176 }
177
178 if probing.has_base_pool {
180 return CurveVariant::StableSwapMeta;
181 }
182
183 if probing.has_int128_balances {
185 return CurveVariant::StableSwapV0;
186 }
187
188 let addr = probing.pool_address;
190 if KNOWN_V0.contains(&addr) {
191 CurveVariant::StableSwapV0
192 } else if KNOWN_V1.contains(&addr) {
193 CurveVariant::StableSwapV1
194 } else if KNOWN_STETH.contains(&addr) {
195 CurveVariant::StableSwapSTETH
196 } else {
197 CurveVariant::StableSwapV2
198 }
199}
200
201const KNOWN_TRICRYPTO_V1: [Address; 2] = [
208 address!("D51a44d3FaE010294C616388b506AcdA1bfAAE46"), address!("80466c64868E1ab14a1Ddf27A676C3fcBE638Fe5"), ];
211
212const KNOWN_V0: [Address; 8] = [
213 address!("A5407eAE9Ba41422680e2e00537571bcC53efBfD"), address!("A2B47E3D5c44877cca798226B7B8118F9BFb7A56"), address!("79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27"), address!("45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51"), address!("52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C"), address!("06364f10B501e868329afBc005b3492902d6C763"), address!("93054188d876f558f4a66B2EF1d97d16eDf0895B"), address!("7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714"), ];
222
223const KNOWN_V1: [Address; 2] = [
224 address!("bEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"), address!("4CA9b3063Ec5866A4B82E437059D2C43d1be596F"), ];
227
228const KNOWN_STETH: [Address; 1] = [
233 address!("DC24316b9AE028F1497c275EB9192a3Ea0f67022"), ];
235
236const NON_ETH_TWOCRYPTO_V1: [Address; 0] = [];
258
259pub fn detect_eth_variant(pool: Address) -> bool {
265 !NON_ETH_TWOCRYPTO_V1.contains(&pool)
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 fn addr(s: &str) -> Address {
273 s.parse().unwrap()
274 }
275
276 #[test]
277 fn detect_tricrypto_v1_by_address() {
278 let probing = ProbingResults {
279 has_gamma: true,
280 n_coins: 3,
281 has_math: false,
282 math_version: None,
283 has_offpeg_fee_multiplier: false,
284 has_stored_rates: false,
285 has_version: false,
286 has_base_pool: false,
287 has_int128_balances: false,
288 pool_address: addr("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"),
289 };
290 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TriCryptoV1);
291 }
292
293 #[test]
294 fn detect_tricrypto_ng_unknown_3coin() {
295 let probing = ProbingResults {
296 has_gamma: true,
297 n_coins: 3,
298 has_math: false,
299 math_version: None,
300 has_offpeg_fee_multiplier: false,
301 has_stored_rates: false,
302 has_version: false,
303 has_base_pool: false,
304 has_int128_balances: false,
305 pool_address: addr("0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B"),
306 };
307 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TriCryptoNG);
308 }
309
310 #[test]
311 fn detect_twocrypto_ng_v200() {
312 let probing = ProbingResults {
313 has_gamma: true,
314 n_coins: 2,
315 has_math: true,
316 math_version: Some("v2.0.0".to_string()),
317 has_offpeg_fee_multiplier: false,
318 has_stored_rates: false,
319 has_version: false,
320 has_base_pool: false,
321 has_int128_balances: false,
322 pool_address: addr("0xfb8b95Fb2296a0Ad4b6b1419fdAA5AA5F13e4009"),
323 };
324 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoNG);
325 }
326
327 #[test]
328 fn detect_twocrypto_ng_v210() {
329 let probing = ProbingResults {
330 has_gamma: true,
331 n_coins: 2,
332 has_math: true,
333 math_version: Some("v2.1.0".to_string()),
334 has_offpeg_fee_multiplier: false,
335 has_stored_rates: false,
336 has_version: false,
337 has_base_pool: false,
338 has_int128_balances: false,
339 pool_address: Address::ZERO,
340 };
341 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoNG);
342 }
343
344 #[test]
345 fn detect_twocrypto_stable_v010() {
346 let probing = ProbingResults {
347 has_gamma: true,
348 n_coins: 2,
349 has_math: true,
350 math_version: Some("v0.1.0".to_string()),
351 has_offpeg_fee_multiplier: false,
352 has_stored_rates: false,
353 has_version: false,
354 has_base_pool: false,
355 has_int128_balances: false,
356 pool_address: addr("0x6e5492F8ea2370844EE098A56DD88e1717e4A9C2"),
357 };
358 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoStable);
359 }
360
361 #[test]
362 fn detect_twocrypto_v1_no_math() {
363 let probing = ProbingResults {
364 has_gamma: true,
365 n_coins: 2,
366 has_math: false,
367 math_version: None,
368 has_offpeg_fee_multiplier: false,
369 has_stored_rates: false,
370 has_version: false,
371 has_base_pool: false,
372 has_int128_balances: false,
373 pool_address: addr("0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511"),
374 };
375 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoV1);
376 }
377
378 #[test]
379 fn detect_twocrypto_ng_unknown_math_version() {
380 let probing = ProbingResults {
381 has_gamma: true,
382 n_coins: 2,
383 has_math: true,
384 math_version: Some("v3.0.0".to_string()),
385 has_offpeg_fee_multiplier: false,
386 has_stored_rates: false,
387 has_version: false,
388 has_base_pool: false,
389 has_int128_balances: false,
390 pool_address: Address::ZERO,
391 };
392 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::TwoCryptoNG);
394 }
395
396 #[test]
397 fn detect_crypto_unsupported_coin_count() {
398 let probing = ProbingResults {
399 has_gamma: true,
400 n_coins: 4,
401 has_math: false,
402 math_version: None,
403 has_offpeg_fee_multiplier: false,
404 has_stored_rates: false,
405 has_version: false,
406 has_base_pool: false,
407 has_int128_balances: false,
408 pool_address: Address::ZERO,
409 };
410 assert!(detect_variant(&probing).is_err());
411 }
412
413 #[test]
414 fn detect_stableswap_ng() {
415 let probing = ProbingResults {
416 has_gamma: false,
417 n_coins: 2,
418 has_math: false,
419 math_version: None,
420 has_offpeg_fee_multiplier: true,
421 has_stored_rates: true,
422 has_version: true,
423 has_base_pool: false,
424 has_int128_balances: false,
425 pool_address: addr("0xF36a4BA50C603204c3FC6d2dA8b78A7b69CBC67d"),
426 };
427 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapNG);
428 }
429
430 #[test]
431 fn detect_stableswap_ng_without_offpeg() {
432 let probing = ProbingResults {
434 has_gamma: false,
435 n_coins: 2,
436 has_math: false,
437 math_version: None,
438 has_offpeg_fee_multiplier: false,
439 has_stored_rates: true,
440 has_version: true,
441 has_base_pool: false,
442 has_int128_balances: false,
443 pool_address: addr("0x1539c2461d7432cc114b0903f1824079BfCA2C92"),
444 };
445 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapNG);
446 }
447
448 #[test]
449 fn detect_stableswap_ng_via_version() {
450 let probing = ProbingResults {
452 has_gamma: false,
453 n_coins: 3,
454 has_math: false,
455 math_version: None,
456 has_offpeg_fee_multiplier: false,
457 has_stored_rates: false,
458 has_version: true,
459 has_base_pool: false,
460 has_int128_balances: false,
461 pool_address: addr("0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E"),
462 };
463 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapNG);
464 }
465
466 #[test]
467 fn detect_stableswap_alend() {
468 let probing = ProbingResults {
469 has_gamma: false,
470 n_coins: 3,
471 has_math: false,
472 math_version: None,
473 has_offpeg_fee_multiplier: true,
474 has_stored_rates: false,
475 has_version: false,
476 has_base_pool: false,
477 has_int128_balances: false,
478 pool_address: addr("0xDeBF20617708857ebe4F679508E7b7863a8A8EeE"),
479 };
480 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapALend);
481 }
482
483 #[test]
484 fn detect_stableswap_meta() {
485 let probing = ProbingResults {
486 has_gamma: false,
487 n_coins: 2,
488 has_math: false,
489 math_version: None,
490 has_offpeg_fee_multiplier: false,
491 has_stored_rates: false,
492 has_version: false,
493 has_base_pool: true,
494 has_int128_balances: false,
495 pool_address: addr("0x4f062658EaAF2C1ccf8C8e36D6824CDf41167956"),
496 };
497 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapMeta);
498 }
499
500 #[test]
501 fn detect_stableswap_v0_by_int128() {
502 let probing = ProbingResults {
503 has_gamma: false,
504 n_coins: 4,
505 has_math: false,
506 math_version: None,
507 has_offpeg_fee_multiplier: false,
508 has_stored_rates: false,
509 has_version: false,
510 has_base_pool: false,
511 has_int128_balances: true,
512 pool_address: addr("0xA5407eAE9Ba41422680e2e00537571bcC53efBfD"),
513 };
514 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV0);
515 }
516
517 #[test]
518 fn detect_stableswap_v0_by_known_address() {
519 let probing = ProbingResults {
521 has_gamma: false,
522 n_coins: 4,
523 has_math: false,
524 math_version: None,
525 has_offpeg_fee_multiplier: false,
526 has_stored_rates: false,
527 has_version: false,
528 has_base_pool: false,
529 has_int128_balances: false,
530 pool_address: addr("0xA5407eAE9Ba41422680e2e00537571bcC53efBfD"),
531 };
532 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV0);
533 }
534
535 #[test]
536 fn detect_stableswap_v1_3pool() {
537 let probing = ProbingResults {
538 has_gamma: false,
539 n_coins: 3,
540 has_math: false,
541 math_version: None,
542 has_offpeg_fee_multiplier: false,
543 has_stored_rates: false,
544 has_version: false,
545 has_base_pool: false,
546 has_int128_balances: false,
547 pool_address: addr("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"),
548 };
549 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV1);
550 }
551
552 #[test]
553 fn detect_stableswap_steth_by_known_address() {
554 let probing = ProbingResults {
557 has_gamma: false,
558 n_coins: 2,
559 has_math: false,
560 math_version: None,
561 has_offpeg_fee_multiplier: false,
562 has_stored_rates: false,
563 has_version: false,
564 has_base_pool: false,
565 has_int128_balances: false,
566 pool_address: addr("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),
567 };
568 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapSTETH);
569 }
570
571 #[test]
572 fn detect_eth_variant_defaults_true() {
573 assert!(detect_eth_variant(addr("0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511")));
576 assert!(detect_eth_variant(Address::ZERO));
577 }
578
579 #[test]
580 fn detect_stableswap_v2_default() {
581 let probing = ProbingResults {
582 has_gamma: false,
583 n_coins: 2,
584 has_math: false,
585 math_version: None,
586 has_offpeg_fee_multiplier: false,
587 has_stored_rates: false,
588 has_version: false,
589 has_base_pool: false,
590 has_int128_balances: false,
591 pool_address: addr("0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2"),
592 };
593 assert_eq!(detect_variant(&probing).unwrap(), CurveVariant::StableSwapV2);
594 }
595}