diff --git a/rust/Cargo.lock b/rust/Cargo.lock index aede8fe..df1e297 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2975,6 +2975,7 @@ name = "sanctum-lst-list" version = "0.2.0" dependencies = [ "borsh 0.9.3", + "lazy_static", "mpl-token-metadata", "reqwest", "sanctum-macros", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ab6042e..2bf849d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,6 +6,7 @@ members = ["sanctum-lst-list", "tojup"] borsh = "^0.9" clap = { version = "^4", features = ["derive"] } csv = "^1" +lazy_static = "^1" mpl-token-metadata = "^4" reqwest = "^0.11" sanctum-macros = "^1" diff --git a/rust/sanctum-lst-list/Cargo.toml b/rust/sanctum-lst-list/Cargo.toml index f951de7..eca1343 100644 --- a/rust/sanctum-lst-list/Cargo.toml +++ b/rust/sanctum-lst-list/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" include = ["README.md", "sanctum-lst-list.toml", "src/"] +[features] +default = [] +test-all = [] + [dependencies] serde = { workspace = true } serde_with = { workspace = true } @@ -14,6 +18,7 @@ toml = { workspace = true } [dev-dependencies] borsh = { workspace = true } +lazy_static = { workspace = true } mpl-token-metadata = { workspace = true } reqwest = { workspace = true } solana-client = { workspace = true } diff --git a/rust/sanctum-lst-list/tests/common/mod.rs b/rust/sanctum-lst-list/tests/common/mod.rs index 5d4c4aa..bace158 100644 --- a/rust/sanctum-lst-list/tests/common/mod.rs +++ b/rust/sanctum-lst-list/tests/common/mod.rs @@ -1 +1,16 @@ +use lazy_static::lazy_static; +use sanctum_lst_list::{SanctumLst, SanctumLstList}; + +lazy_static! { + pub static ref SANCTUM_LST_LIST: SanctumLstList = SanctumLstList::load(); +} + pub const SOLANA_RPC_URL: &str = "https://api.mainnet-beta.solana.com"; + +pub fn find_sanctum_lst_by_symbol_unwrapped(symbol: &str) -> &SanctumLst { + SANCTUM_LST_LIST + .sanctum_lst_list + .iter() + .find(|lst| lst.symbol == symbol) + .unwrap() +} diff --git a/rust/sanctum-lst-list/tests/tests/logo.rs b/rust/sanctum-lst-list/tests/tests/logo.rs index 95387ab..3fba6e9 100644 --- a/rust/sanctum-lst-list/tests/tests/logo.rs +++ b/rust/sanctum-lst-list/tests/tests/logo.rs @@ -1,29 +1,30 @@ use std::error::Error; use reqwest::header; -use sanctum_lst_list::{SanctumLst, SanctumLstList}; -use tokio::task::JoinSet; +use sanctum_lst_list::SanctumLst; + +use crate::common::find_sanctum_lst_by_symbol_unwrapped; + +// Tests for latest batch #[tokio::test] -async fn verify_all_token_logo_image_uri_valid() { - let client: &'static reqwest::Client = Box::leak(Box::new(reqwest::Client::new())); - let SanctumLstList { sanctum_lst_list } = SanctumLstList::load(); - let mut js = JoinSet::new(); - sanctum_lst_list.into_iter().for_each(|slst| { - js.spawn(verify_token_logo_image_uri_valid(client, slst)); - }); - while let Some(res) = js.join_next().await { - res.unwrap(); - } +async fn verify_logo_image_uri_valid_rugsol() { + verify_token_logo_image_uri_valid_by_symbol("rugSOL").await; +} + +async fn verify_token_logo_image_uri_valid_by_symbol(symbol: &str) { + let client = reqwest::Client::new(); + let sanctum_lst = find_sanctum_lst_by_symbol_unwrapped(symbol); + verify_token_logo_image_uri_valid(&client, sanctum_lst).await; } async fn verify_token_logo_image_uri_valid( client: &reqwest::Client, SanctumLst { logo_uri, symbol, .. - }: SanctumLst, + }: &SanctumLst, ) { - let content_type = match fetch_logo_image_uri_content_type(client, &logo_uri).await { + let content_type = match fetch_logo_image_uri_content_type(client, logo_uri).await { Ok(ct) => ct, Err(e) => panic!("{symbol} fetch failed: {e}"), }; @@ -48,3 +49,19 @@ async fn fetch_logo_image_uri_content_type( .to_str()? .to_owned()) } + +#[cfg(feature = "test-all")] +#[tokio::test] +async fn verify_all_token_logo_image_uri_valid() { + let client: &'static reqwest::Client = Box::leak(Box::new(reqwest::Client::new())); + let mut js = tokio::task::JoinSet::new(); + crate::common::SANCTUM_LST_LIST + .sanctum_lst_list + .iter() + .for_each(|slst| { + js.spawn(verify_token_logo_image_uri_valid(client, slst)); + }); + while let Some(res) = js.join_next().await { + res.unwrap(); + } +} diff --git a/rust/sanctum-lst-list/tests/tests/metadata.rs b/rust/sanctum-lst-list/tests/tests/metadata.rs index d32fc6c..620ceba 100644 --- a/rust/sanctum-lst-list/tests/tests/metadata.rs +++ b/rust/sanctum-lst-list/tests/tests/metadata.rs @@ -1,59 +1,18 @@ use std::{collections::HashSet, error::Error}; use mpl_token_metadata::accounts::Metadata; -use sanctum_lst_list::{SanctumLst, SanctumLstList}; +use sanctum_lst_list::SanctumLst; use serde::Deserialize; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::commitment_config::CommitmentConfig; -use crate::common::SOLANA_RPC_URL; +use crate::common::{find_sanctum_lst_by_symbol_unwrapped, SOLANA_RPC_URL}; -#[tokio::test] -async fn verify_all_lsts_token_metadata() { - let client: &'static reqwest::Client = Box::leak(Box::new(reqwest::Client::new())); - let rpc = RpcClient::new(SOLANA_RPC_URL.to_owned()); - let SanctumLstList { sanctum_lst_list } = SanctumLstList::load(); - // just do it sequentially to avoid rpc limits - let mut no_metadata_lsts = Vec::new(); - - let ignore_symbols = [ - "wifSOL", // name is `dogwifSOL` but onchain is `wifSOL` - "juicingJupSOL", // symbol onchain is "jjupSOL" - "fmSOL", // name is `SolanaFM Staked SOL` but onchain is `fmSOL` - "dSOL", // name is `Drift Staked SOL` but onchain is `Drift Staked Sol` - "stakeSOL", // name is `Stake City SOL` but onchain is `stakeSOL` - "pumpkinSOL", // name is `Pumpkin's Staked SOL` but onchain is `pumpkinSOL` - "JSOL", // name is `JPOOL Solana Token` but onchain is `JPool Staked SOL` - "thugSOL", // name is `Thugbirdz Staked SOL` but onchain is `ThugBirdz Staked SOL` - "wenSOL", // symbol onchain is `WenSOL` - "dlgtSOL", // name is `Delegate Liquid Staking SOL` but onchain is `Delegate Liquid Staking Token` - "hausSOL", // name is `StakeHaus Staked SOL` but onchain is `hausSOL` - "rSOL", // name is `reflectSOL` but onchain is `Reflect Staked Solana` - "xSOL", // name is `ElagabalX Staked SOL` but onchain is `xSOL` - "stepSOL", // name is `Step Staked SOL` but onchain is `stepSOL` - "SOL", // name is `SOL` but onchain is `Wrapped SOL` - "mSOL", // name is `Marinade staked SOL` but onchain is `Marinade staked SOL (mSOL)` - "spikySOL", // malformed linked metadata json - "pineSOL", // metadata URI links to logo instead of json - "uwuSOL", // name is `UwU Staked SOL` but onchain is Uwu Staked SOL` - ] - .into_iter() - .collect(); +// Tests for latest batch - for sanctum_lst in sanctum_lst_list.iter() { - match verify_lst_token_metadata(client, &rpc, sanctum_lst, &ignore_symbols).await { - Ok(Some(..)) => (), - Ok(None) => { - no_metadata_lsts.push(&sanctum_lst.symbol); - } - Err(e) => { - panic!("{}: {}", sanctum_lst.symbol, e); - } - } - } - if !no_metadata_lsts.is_empty() { - eprintln!("LSTs with no onchain token metadata: {no_metadata_lsts:?}"); - } +#[tokio::test] +async fn verify_lst_token_metadata_rugsol() { + verify_lst_token_metadata_by_symbol("rugSOL").await; } #[derive(Debug, Deserialize)] @@ -61,6 +20,15 @@ struct OffchainMetadata { image: String, } +async fn verify_lst_token_metadata_by_symbol(symbol: &str) { + let client = reqwest::Client::new(); + let rpc = RpcClient::new(SOLANA_RPC_URL.to_owned()); + let sanctum_lst = find_sanctum_lst_by_symbol_unwrapped(symbol); + verify_lst_token_metadata(&client, &rpc, sanctum_lst, &HashSet::new()) + .await + .unwrap(); +} + async fn verify_lst_token_metadata( client: &reqwest::Client, rpc: &RpcClient, @@ -119,3 +87,51 @@ async fn verify_lst_token_metadata( Ok(Some(())) } + +#[cfg(feature = "test-all")] +#[tokio::test] +async fn verify_all_lsts_token_metadata() { + let client: &'static reqwest::Client = Box::leak(Box::new(reqwest::Client::new())); + let rpc = RpcClient::new(SOLANA_RPC_URL.to_owned()); + // just do it sequentially to avoid rpc limits + let mut no_metadata_lsts = Vec::new(); + + let ignore_symbols = [ + "wifSOL", // name is `dogwifSOL` but onchain is `wifSOL` + "juicingJupSOL", // symbol onchain is "jjupSOL" + "fmSOL", // name is `SolanaFM Staked SOL` but onchain is `fmSOL` + "dSOL", // name is `Drift Staked SOL` but onchain is `Drift Staked Sol` + "stakeSOL", // name is `Stake City SOL` but onchain is `stakeSOL` + "pumpkinSOL", // name is `Pumpkin's Staked SOL` but onchain is `pumpkinSOL` + "JSOL", // name is `JPOOL Solana Token` but onchain is `JPool Staked SOL` + "thugSOL", // name is `Thugbirdz Staked SOL` but onchain is `ThugBirdz Staked SOL` + "wenSOL", // symbol onchain is `WenSOL` + "dlgtSOL", // name is `Delegate Liquid Staking SOL` but onchain is `Delegate Liquid Staking Token` + "hausSOL", // name is `StakeHaus Staked SOL` but onchain is `hausSOL` + "rSOL", // name is `reflectSOL` but onchain is `Reflect Staked Solana` + "xSOL", // name is `ElagabalX Staked SOL` but onchain is `xSOL` + "stepSOL", // name is `Step Staked SOL` but onchain is `stepSOL` + "SOL", // name is `SOL` but onchain is `Wrapped SOL` + "mSOL", // name is `Marinade staked SOL` but onchain is `Marinade staked SOL (mSOL)` + "spikySOL", // malformed linked metadata json + "pineSOL", // metadata URI links to logo instead of json + "uwuSOL", // name is `UwU Staked SOL` but onchain is Uwu Staked SOL` + ] + .into_iter() + .collect(); + + for sanctum_lst in crate::common::SANCTUM_LST_LIST.sanctum_lst_list.iter() { + match verify_lst_token_metadata(client, &rpc, sanctum_lst, &ignore_symbols).await { + Ok(Some(..)) => (), + Ok(None) => { + no_metadata_lsts.push(&sanctum_lst.symbol); + } + Err(e) => { + panic!("{}: {}", sanctum_lst.symbol, e); + } + } + } + if !no_metadata_lsts.is_empty() { + eprintln!("LSTs with no onchain token metadata: {no_metadata_lsts:?}"); + } +} diff --git a/rust/sanctum-lst-list/tests/tests/pool.rs b/rust/sanctum-lst-list/tests/tests/pool.rs index 1bff9a1..fc71285 100644 --- a/rust/sanctum-lst-list/tests/tests/pool.rs +++ b/rust/sanctum-lst-list/tests/tests/pool.rs @@ -1,21 +1,23 @@ use borsh::BorshDeserialize; -use sanctum_lst_list::{PoolInfo, SanctumLst, SanctumLstList}; +use sanctum_lst_list::{PoolInfo, SanctumLst}; use solana_client::rpc_client::RpcClient; use solana_program::pubkey::Pubkey; use spl_stake_pool_interface::{StakePool, ValidatorList, ValidatorStakeInfo}; use spl_token_2022::extension::StateWithExtensions; -use crate::common::SOLANA_RPC_URL; +use crate::common::{find_sanctum_lst_by_symbol_unwrapped, SOLANA_RPC_URL}; + +// Tests for latest batch -// this takes around 30s with around 70 pools #[test] -fn verify_all_pools_valid() { +fn verify_pool_valid_rugsol() { + verify_pool_valid_by_symbol("rugSOL"); +} + +fn verify_pool_valid_by_symbol(symbol: &str) { let rpc = RpcClient::new(SOLANA_RPC_URL); - let SanctumLstList { sanctum_lst_list } = SanctumLstList::load(); - // just do it sequentially to avoid rpc limits - for sanctum_lst in sanctum_lst_list { - verify_pool_valid(&rpc, &sanctum_lst); - } + let sanctum_lst = find_sanctum_lst_by_symbol_unwrapped(symbol); + verify_pool_valid(&rpc, sanctum_lst); } fn verify_pool_valid( @@ -112,3 +114,14 @@ fn verify_pool_valid( } } } + +// this takes around 30s with around 70 pools +#[cfg(feature = "test-all")] +#[test] +fn verify_all_pools_valid() { + let rpc = RpcClient::new(SOLANA_RPC_URL); + // just do it sequentially to avoid rpc limits + for sanctum_lst in crate::common::SANCTUM_LST_LIST.sanctum_lst_list.iter() { + verify_pool_valid(&rpc, sanctum_lst); + } +} diff --git a/rust/sanctum-lst-list/tests/tests/sanctum_router.rs b/rust/sanctum-lst-list/tests/tests/sanctum_router.rs index 1570540..20f876c 100644 --- a/rust/sanctum-lst-list/tests/tests/sanctum_router.rs +++ b/rust/sanctum-lst-list/tests/tests/sanctum_router.rs @@ -1,11 +1,17 @@ use std::error::Error; -use sanctum_lst_list::SanctumLstList; use solana_client::rpc_client::RpcClient; use solana_program::pubkey::Pubkey; use spl_token_2022::{extension::StateWithExtensions, state::AccountState}; -use crate::common::SOLANA_RPC_URL; +use crate::common::{find_sanctum_lst_by_symbol_unwrapped, SOLANA_RPC_URL}; + +// Tests for latest batch + +#[test] +fn verify_sanctum_router_fee_token_acc_created_rugsol() { + verify_sanctum_router_fee_token_acc_created_by_symbol("rugSOL").unwrap(); +} // Copied from // https://github.com/igneous-labs/stakedex-sdk/blob/master/common/src/pda.rs @@ -25,16 +31,12 @@ fn find_fee_token_acc(mint: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&fee_token_account_seeds(mint), &stakedex_interface::ID) } -#[test] -fn verify_all_lsts_have_router_fee_token_acc_created() { +fn verify_sanctum_router_fee_token_acc_created_by_symbol( + symbol: &str, +) -> Result<(), Box> { let rpc = RpcClient::new(SOLANA_RPC_URL); - let SanctumLstList { sanctum_lst_list } = SanctumLstList::load(); - // just do it sequentially to avoid rpc limits - for sanctum_lst in sanctum_lst_list { - if let Err(e) = verify_sanctum_router_fee_token_acc_created(&rpc, &sanctum_lst.mint) { - panic!("{}: {}", sanctum_lst.symbol, e); - } - } + let slst = find_sanctum_lst_by_symbol_unwrapped(symbol); + verify_sanctum_router_fee_token_acc_created(&rpc, &slst.mint) } fn verify_sanctum_router_fee_token_acc_created( @@ -54,3 +56,15 @@ fn verify_sanctum_router_fee_token_acc_created( } Ok(()) } + +#[cfg(feature = "test-all")] +#[test] +fn verify_all_lsts_have_router_fee_token_acc_created() { + let rpc = RpcClient::new(SOLANA_RPC_URL); + // just do it sequentially to avoid rpc limits + for sanctum_lst in crate::common::SANCTUM_LST_LIST.sanctum_lst_list.iter() { + if let Err(e) = verify_sanctum_router_fee_token_acc_created(&rpc, &sanctum_lst.mint) { + panic!("{}: {}", sanctum_lst.symbol, e); + } + } +}