diff --git a/scripts/validate-tokens/Cargo.lock b/scripts/validate-tokens/Cargo.lock index 893a690..3f806f2 100644 --- a/scripts/validate-tokens/Cargo.lock +++ b/scripts/validate-tokens/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -493,6 +493,56 @@ dependencies = [ "url", ] +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -823,6 +873,52 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "const-hex" version = "1.14.1" @@ -1628,6 +1724,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -1883,6 +1985,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "openssl" version = "0.10.72" @@ -2667,6 +2775,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.27.1" @@ -2695,6 +2809,15 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + [[package]] name = "syn" version = "1.0.109" @@ -3040,14 +3163,22 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "validate-tokens" version = "0.1.0" dependencies = [ "alloy", "base64 0.21.7", + "clap", "serde", "serde_json", + "subtle-encoding", "tokio", ] diff --git a/scripts/validate-tokens/Cargo.toml b/scripts/validate-tokens/Cargo.toml index 46484f7..70aa3d3 100644 --- a/scripts/validate-tokens/Cargo.toml +++ b/scripts/validate-tokens/Cargo.toml @@ -9,3 +9,5 @@ tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" base64 = "0.21" +clap = { version = "4.5.39", features = ["derive"] } +subtle-encoding = { version = "0.5", features = ["bech32-preview"] } diff --git a/scripts/validate-tokens/src/main.rs b/scripts/validate-tokens/src/main.rs index ecaa726..0223fc2 100644 --- a/scripts/validate-tokens/src/main.rs +++ b/scripts/validate-tokens/src/main.rs @@ -4,12 +4,20 @@ use alloy::sol; use alloy::transports::http::reqwest::Url; use alloy::transports::http::{Client, Http}; use base64::{engine::general_purpose, Engine as _}; +use clap::Parser; use serde::Deserialize; use std::collections::HashMap; use std::env; use std::fs; use std::str::FromStr; +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(long)] + config: String, +} + sol! { #[sol(rpc)] interface IERC20 { @@ -21,7 +29,8 @@ sol! { #[derive(Debug, Deserialize)] struct Config { - files: HashMap, + path: String, + rpc_type: String, } #[derive(Debug, Deserialize)] @@ -47,47 +56,24 @@ struct TokenInfo { #[tokio::main] async fn main() -> Result<(), Box> { - let config_path = env::args() - .skip_while(|arg| arg != "--config") - .nth(1) - .unwrap_or("tokenlist_config.json".to_string()); - - let config_content = fs::read_to_string(config_path)?; - let config: Config = serde_json::from_str(&config_content)?; - + let args = Args::parse(); + let config_content = fs::read_to_string(args.config)?; + let config: HashMap = serde_json::from_str(&config_content)?; let mut error_count = 0; - for (network, path) in config.files { - println!("\nšŸ” Checking file: {path} for network: {network}"); + for (chain_id, cfg) in config { + println!("\nšŸ” Checking file: {} for network: {}", cfg.path, chain_id); - let content = fs::read_to_string(path)?; + let content = fs::read_to_string(cfg.path)?; let json: serde_json::Value = serde_json::from_str(&content)?; let token_array = json.get("tokens").ok_or("Missing 'tokens' field")?; let tokens: Vec = serde_json::from_value(token_array.clone())?; - if let Some(provider) = get_ethereum_provider(&network) { - for token in tokens { - match verify_on_ethereum(&token, &provider).await { - Ok(errors) => error_count += errors, - Err(e) => { - eprintln!("āŒ Failed to verify {}: {}", token.symbol, e); - error_count += 1; - } - } - } - } else if network == "babylon" { - for token in tokens { - match verify_on_babylon(&token).await { - Ok(errors) => error_count += errors, - Err(e) => { - eprintln!("āŒ Failed to verify {}: {}", token.symbol, e); - error_count += 1; - } - } - } - } else { - println!("āš ļø Unknown network: {}", network); + match cfg.rpc_type.as_str() { + "evm" => error_count += evm(&chain_id, tokens).await, + "cosmos" => error_count += cosmos(&chain_id, tokens).await, + _ => panic!("invalid rpc_type"), } } @@ -100,7 +86,40 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn verify_on_ethereum( +async fn evm(chain_id: &String, tokens: Vec) -> u32 { + let mut error_count = 0; + if let Some(provider) = get_evm_provider(chain_id) { + for token in tokens { + match verify_on_evm(&token, &provider).await { + Ok(erros) => error_count += erros, + Err(e) => { + eprintln!("āŒ Failed to verify {}: {}", token.symbol, e); + error_count += 1; + } + } + } + } else { + println!( + "āš ļø Unable to initialize provider for network: {}", + &chain_id + ); + } + error_count +} + +fn get_evm_provider(network: &str) -> Option>> { + let provider_suffix = env::var("RPC_PROVIDER").ok()?; + let subdomain = match network.split_once('.') { + Some((left, right)) => format!("{}.{}", right, left), + None => network.to_owned(), + }; + let full_url = format!("https://rpc.{subdomain}{provider_suffix}"); + let provider = ProviderBuilder::new().on_http(Url::parse(&full_url).ok()?); + + Some(provider) +} + +async fn verify_on_evm( token: &Token, provider: &RootProvider>, ) -> Result> { @@ -149,41 +168,45 @@ async fn verify_on_ethereum( Ok(local_error_count) } -fn get_ethereum_provider(network: &str) -> Option>> { - let provider_suffix = env::var("RPC_PROVIDER").ok()?; - - let subdomain = match network { - "ethereum" => "1.ethereum", - "bob" => "60808.bob", - "corn" => "21000000.corn", - _ => return None, - }; - - let full_url = format!("https://rpc.{subdomain}{}", provider_suffix); - let provider = ProviderBuilder::new().on_http(Url::parse(&full_url).ok()?); - - Some(provider) -} - -async fn verify_on_babylon(token: &Token) -> Result> { +async fn cosmos(network: &str, tokens: Vec) -> u32 { + let mut error_count = 0; let client = Client::new(); + let rpc_url = get_rpc_url(network).unwrap(); + let msg = serde_json::json!({ "token_info": {} }); + let query = general_purpose::STANDARD.encode(msg.to_string()); - if token.address == "ubbn" { - println!("ā„¹ļø Skipping non-contract token: {}", token.symbol); - return Ok(0); + for token in tokens { + if subtle_encoding::bech32::decode(&token.address).is_ok() { + match cw_20(&token, &rpc_url, &query, &client).await { + Ok(errors) => error_count += errors, + Err(e) => { + eprintln!("āŒ Failed to verify {}: {}", token.symbol, e); + error_count += 1; + } + } + } } - if token.address.starts_with("ibc/") { - println!("ā„¹ļø Skipping IBC token: {}", token.symbol); - return Ok(0); - } + error_count +} - let msg = serde_json::json!({ "token_info": {} }); - let query = general_purpose::STANDARD.encode(msg.to_string()); +fn get_rpc_url(network: &str) -> Option { + let provider_suffix = env::var("RPC_PROVIDER").ok()?; + let subdomain = match network.split_once('.') { + Some((right, left)) => format!("{}.{}", left, right), + None => network.to_owned(), + }; + Some(format!("https://rest.{subdomain}{provider_suffix}")) +} - let rpc_url = "https://babylon.nodes.guru"; +async fn cw_20( + token: &Token, + rpc_url: &str, + query: &str, + client: &Client, +) -> Result> { let url = format!( - "{}/api/cosmwasm/wasm/v1/contract/{}/smart/{}", + "{}/cosmwasm/wasm/v1/contract/{}/smart/{}", rpc_url, token.address, query ); diff --git a/scripts/validate-tokens/tokenlist_config.json b/scripts/validate-tokens/tokenlist_config.json index 462c82f..6d475d7 100644 --- a/scripts/validate-tokens/tokenlist_config.json +++ b/scripts/validate-tokens/tokenlist_config.json @@ -1,8 +1,38 @@ { - "files": { - "ethereum": "../../data/ethereum.1/tokenlist.json", - "bob": "../../data/bob.60808/tokenlist.json", - "corn": "../../data/corn.21000000/tokenlist.json", - "babylon": "../../data/babylon.bbn-1/tokenlist.json" + "corn.21000000": { + "path": "../../data/corn.21000000/tokenlist.json", + "rpc_type": "evm" + }, + "babylon.bbn-1": { + "path": "../../data/babylon.bbn-1/tokenlist.json", + "rpc_type": "cosmos" + }, + "bob.60808": { + "path": "../../data/bob.60808/tokenlist.json", + "rpc_type": "evm" + }, + "ethereum.1": { + "path": "../../data/ethereum.1/tokenlist.json", + "rpc_type": "evm" + }, + "sepolia.11155111": { + "path": "../../data/sepolia.11155111/tokenlist.json", + "rpc_type": "evm" + }, + "corn.21000001": { + "path": "../../data/corn.21000001/tokenlist.json", + "rpc_type": "evm" + }, + "bob.808813": { + "path": "../../data/bob.808813/tokenlist.json", + "rpc_type": "evm" + }, + "holesky.17000": { + "path": "../../data/holesky.17000/tokenlist.json", + "rpc_type": "evm" + }, + "sei.1328": { + "path": "../../data/sei.1328/tokenlist.json", + "rpc_type": "evm" } }