From 37441411c20294251dd40d6e76508e5cddf94d88 Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Mon, 30 Jun 2025 01:20:48 -0500 Subject: [PATCH 1/2] Updated clippy config --- Cargo.toml | 37 ++++++++++++++++++++++++++++ clippy.toml | 4 +++ rust-toolchain.toml | 2 +- src/agent/legacy_schedule.rs | 9 ++++--- src/agent/market_schedule.rs | 11 +++++---- src/agent/metrics.rs | 7 +++--- src/agent/pyth/rpc.rs | 6 ++++- src/agent/services/exporter.rs | 2 +- src/agent/services/keypairs.rs | 1 + src/agent/services/lazer_exporter.rs | 13 ++++++---- src/agent/services/oracle.rs | 4 +++ src/agent/solana.rs | 2 ++ src/agent/state/api.rs | 4 +-- src/agent/state/exporter.rs | 8 ++++-- src/agent/state/oracle.rs | 17 +++++++------ 15 files changed, 95 insertions(+), 32 deletions(-) create mode 100644 clippy.toml diff --git a/Cargo.toml b/Cargo.toml index 97f2fd4..a9de51b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,3 +66,40 @@ panic = 'abort' [profile.dev] panic = 'abort' + +[lints.clippy] +wildcard_dependencies = "deny" + +collapsible_if = "allow" +collapsible_else_if = "allow" + +allow_attributes_without_reason = "warn" + +# Panics +expect_used = "warn" +fallible_impl_from = "warn" +indexing_slicing = "warn" +panic = "warn" +panic_in_result_fn = "warn" +string_slice = "warn" +todo = "warn" +unchecked_duration_subtraction = "warn" +unreachable = "warn" +unwrap_in_result = "warn" +unwrap_used = "warn" + +# Correctness +cast_lossless = "warn" +cast_possible_truncation = "warn" +cast_possible_wrap = "warn" +cast_sign_loss = "warn" +collection_is_never_read = "warn" +match_wild_err_arm = "warn" +path_buf_push_overwrite = "warn" +read_zero_byte_vec = "warn" +same_name_method = "warn" +suspicious_operation_groupings = "warn" +suspicious_xor_used_as_pow = "warn" +unused_self = "warn" +used_underscore_binding = "warn" +while_float = "warn" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..4d90be6 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true +allow-indexing-slicing-in-tests = true +allow-panic-in-tests = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4fef3ca..fdda5b9 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.86.0" +channel = "1.88.0" profile = "minimal" components = ["rustfmt", "clippy"] diff --git a/src/agent/legacy_schedule.rs b/src/agent/legacy_schedule.rs index 3960041..f885f0b 100644 --- a/src/agent/legacy_schedule.rs +++ b/src/agent/legacy_schedule.rs @@ -1,5 +1,5 @@ //! Market hours metadata parsing and evaluation logic -#![allow(deprecated)] +#![allow(deprecated, reason = "publishers depend on legacy schedule")] use { anyhow::{ @@ -46,6 +46,7 @@ pub struct LegacySchedule { pub sun: MHKind, } +#[allow(deprecated, reason = "publishers depend on legacy schedule")] impl LegacySchedule { pub fn all_closed() -> Self { Self { @@ -93,7 +94,7 @@ impl FromStr for LegacySchedule { .trim() .parse() .map_err(|e: ParseError| anyhow!(e)) - .context(format!("Could parse timezone from {:?}", tz_str))?; + .context(format!("Could parse timezone from {tz_str:?}"))?; let mut weekday_schedules = Vec::with_capacity(7); @@ -112,8 +113,7 @@ impl FromStr for LegacySchedule { ))?; let mhkind: MHKind = mhkind_str.trim().parse().context(format!( - "Could not parse {} field from {:?}", - weekday, mhkind_str + "Could not parse {weekday} field from {mhkind_str:?}", ))?; weekday_schedules.push(mhkind); @@ -225,6 +225,7 @@ impl FromStr for MHKind { } #[cfg(test)] +#[allow(clippy::panic_in_result_fn, reason = "")] mod tests { use { super::*, diff --git a/src/agent/market_schedule.rs b/src/agent/market_schedule.rs index 2584f2f..4497fdf 100644 --- a/src/agent/market_schedule.rs +++ b/src/agent/market_schedule.rs @@ -1,6 +1,6 @@ //! Holiday hours metadata parsing and evaluation logic -#[allow(deprecated)] +#[allow(deprecated, reason = "publishers depend on legacy schedule")] use { super::legacy_schedule::{ LegacySchedule, @@ -68,14 +68,14 @@ impl Display for MarketSchedule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{};", self.timezone)?; for (i, day) in self.weekly_schedule.iter().enumerate() { - write!(f, "{}", day)?; + write!(f, "{day}")?; if i < 6 { write!(f, ",")?; } } write!(f, ";")?; for (i, holiday) in self.holidays.iter().enumerate() { - write!(f, "{}", holiday)?; + write!(f, "{holiday}")?; if i < self.holidays.len() - 1 { write!(f, ",")?; } @@ -131,7 +131,7 @@ impl FromStr for MarketSchedule { } } -#[allow(deprecated)] +#[allow(deprecated, reason = "publishers still depend on legacy schedule")] impl From for MarketSchedule { fn from(legacy: LegacySchedule) -> Self { Self { @@ -283,6 +283,7 @@ impl From for ScheduleDayKind { } #[cfg(test)] +#[allow(clippy::panic_in_result_fn, reason = "")] mod tests { use { super::*, @@ -604,7 +605,7 @@ mod tests { #[test] fn parse_valid_holiday_day_schedule(s in VALID_SCHEDULE_DAY_KIND_REGEX, d in VALID_MONTH_DAY_REGEX) { - let valid_holiday_day = format!("{}/{}", d, s); + let valid_holiday_day = format!("{d}/{s}"); assert!(valid_holiday_day.parse::().is_ok()); } diff --git a/src/agent/metrics.rs b/src/agent/metrics.rs index bbf84e2..9e8e9f5 100644 --- a/src/agent/metrics.rs +++ b/src/agent/metrics.rs @@ -35,6 +35,7 @@ use { }; pub fn default_bind_address() -> SocketAddr { + #[allow(clippy::unwrap_used, reason = "hardcoded value valid")] "127.0.0.1:8888".parse().unwrap() } @@ -63,7 +64,7 @@ pub async fn spawn(addr: impl Into + 'static) { .and(warp::path::end()) .and_then(move || async move { let mut buf = String::new(); - #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrow, reason = "false positive")] let response = encode(&mut buf, &&PROMETHEUS_REGISTRY.lock().await) .map_err(|e| -> Box { e.into() }) .map(|_| Box::new(reply::with_status(buf, StatusCode::OK))) @@ -118,7 +119,7 @@ impl ProductGlobalMetrics { pub fn update(&self, product_key: &Pubkey, maybe_symbol: Option) { let symbol_string = maybe_symbol .map(|x| x.into()) - .unwrap_or(format!("unknown_{}", product_key)); + .unwrap_or(format!("unknown_{product_key}")); #[deny(unused_variables)] let Self { update_count } = self; @@ -248,7 +249,7 @@ impl PriceGlobalMetrics { expo.get_or_create(&PriceGlobalLabels { pubkey: price_key.to_string(), }) - .set(price_account.expo as i64); + .set(i64::from(price_account.expo)); conf.get_or_create(&PriceGlobalLabels { pubkey: price_key.to_string(), diff --git a/src/agent/pyth/rpc.rs b/src/agent/pyth/rpc.rs index be200f8..7af31c1 100644 --- a/src/agent/pyth/rpc.rs +++ b/src/agent/pyth/rpc.rs @@ -170,7 +170,7 @@ async fn handle_connection( } } -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, reason = "")] async fn handle_next( state: &S, ws_tx: &mut SplitSink, @@ -266,6 +266,10 @@ where if is_batch { feed_text(ws_tx, &serde_json::to_string(&responses)?).await?; } else { + #[allow( + clippy::indexing_slicing, + reason = "single response guaranteed to have one item" + )] feed_text(ws_tx, &serde_json::to_string(&responses[0])?).await?; } } diff --git a/src/agent/services/exporter.rs b/src/agent/services/exporter.rs index 58119a3..f2d73e5 100644 --- a/src/agent/services/exporter.rs +++ b/src/agent/services/exporter.rs @@ -210,7 +210,7 @@ where handles } -#[allow(clippy::module_inception)] +#[allow(clippy::module_inception, reason = "")] mod exporter { use { super::NetworkState, diff --git a/src/agent/services/keypairs.rs b/src/agent/services/keypairs.rs index 528b4b6..7538d04 100644 --- a/src/agent/services/keypairs.rs +++ b/src/agent/services/keypairs.rs @@ -38,6 +38,7 @@ use { const DEFAULT_MIN_KEYPAIR_BALANCE_SOL: u64 = 1; pub fn default_bind_address() -> SocketAddr { + #[allow(clippy::expect_used, reason = "hardcoded value valid")] "127.0.0.1:9001" .parse() .expect("INTERNAL: Could not build default remote keypair loader bind address") diff --git a/src/agent/services/lazer_exporter.rs b/src/agent/services/lazer_exporter.rs index 33e34df..c6d46ff 100644 --- a/src/agent/services/lazer_exporter.rs +++ b/src/agent/services/lazer_exporter.rs @@ -282,6 +282,7 @@ fn get_signing_key(config: &Config) -> Result { pub fn lazer_exporter(config: Config, state: Arc) -> Vec> { let mut handles = vec![]; + #[allow(clippy::panic, reason = "agent can't work without keypair")] let signing_key = match get_signing_key(&config) { Ok(signing_key) => signing_key, Err(e) => { @@ -324,7 +325,7 @@ pub fn lazer_exporter(config: Config, state: Arc) -> Vec { @@ -435,6 +437,7 @@ mod lazer_exporter { let source_timestamp_micros = price_info.timestamp.and_utc().timestamp_micros(); let source_timestamp = MessageField::some(Timestamp { seconds: source_timestamp_micros / 1_000_000, + #[allow(clippy::cast_possible_truncation, reason = "value is always less than one billion")] nanos: (source_timestamp_micros % 1_000_000 * 1000) as i32, special_fields: Default::default(), }); @@ -710,7 +713,7 @@ mod tests { let mut temp_file = NamedTempFile::new().unwrap(); temp_file .as_file_mut() - .write(private_key_string.as_bytes()) + .write_all(private_key_string.as_bytes()) .unwrap(); temp_file.flush().unwrap(); temp_file @@ -758,8 +761,8 @@ mod tests { .unwrap(); let price = PriceInfo { status: PriceStatus::Trading, - price: 100_000_00000000i64, - conf: 1_00000000u64, + price: 10_000_000_000_000i64, + conf: 100_000_000u64, timestamp: Default::default(), }; state.update(btc_id, price).await.unwrap(); @@ -787,7 +790,7 @@ mod tests { } else { panic!("expected price_update") }; - assert_eq!(price_update.price, Some(100_000_00000000i64)); + assert_eq!(price_update.price, Some(10_000_000_000_000i64)); } _ => panic!("channel should have a transaction waiting"), } diff --git a/src/agent/services/oracle.rs b/src/agent/services/oracle.rs index 41f3cdd..b51d760 100644 --- a/src/agent/services/oracle.rs +++ b/src/agent/services/oracle.rs @@ -67,6 +67,10 @@ where let sleep_time = config.oracle.subscriber_finished_sleep_time; let mut wss_url_index: usize = 0; + #[allow( + clippy::indexing_slicing, + reason = "index will always be valid unless wss_urls is empty" + )] handles.push(tokio::spawn(async move { loop { let current_time = Instant::now(); diff --git a/src/agent/solana.rs b/src/agent/solana.rs index 0088a79..ec3e247 100644 --- a/src/agent/solana.rs +++ b/src/agent/solana.rs @@ -26,10 +26,12 @@ pub mod network { Secondary, } + #[allow(clippy::unwrap_used, reason = "hardcoded value valid")] pub fn default_rpc_urls() -> Vec { vec![Url::parse("http://localhost:8899").unwrap()] } + #[allow(clippy::unwrap_used, reason = "hardcoded value valid")] pub fn default_wss_urls() -> Vec { vec![Url::parse("http://localhost:8900").unwrap()] } diff --git a/src/agent/state/api.rs b/src/agent/state/api.rs index f27ee9e..b5e5a1b 100644 --- a/src/agent/state/api.rs +++ b/src/agent/state/api.rs @@ -112,7 +112,7 @@ fn solana_price_account_to_pythd_api_price_account( PriceAccount { account: price_account_key.to_smolstr(), price_type: "price".into(), - price_exponent: price_account.expo as i64, + price_exponent: i64::from(price_account.expo), status: price_status_to_str(price_account.agg.status), price: price_account.agg.price, conf: price_account.agg.conf, @@ -228,7 +228,7 @@ where .map(|(price_account_key, price_account)| PriceAccountMetadata { account: price_account_key.to_smolstr(), price_type: "price".into(), - price_exponent: price_account.expo as i64, + price_exponent: i64::from(price_account.expo), }) .collect(); diff --git a/src/agent/state/exporter.rs b/src/agent/state/exporter.rs index 28c803d..32e6838 100644 --- a/src/agent/state/exporter.rs +++ b/src/agent/state/exporter.rs @@ -446,7 +446,7 @@ async fn estimate_compute_unit_price_micro_lamports( /// - Degrade gracefully if the blockchain RPC node exhibits poor performance. If the RPC node takes a long /// time to respond, no internal queues grow unboundedly. At any single point in time there are at most /// (n / batch_size) requests in flight. -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, reason = "")] #[instrument( skip(state, rpc_multi_client, network_state_rx, publish_keypair, staleness_threshold, permissioned_updates), fields( @@ -528,7 +528,7 @@ where Ok(()) } -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, reason = "")] #[instrument( skip(state, rpc_multi_client, network_state, publish_keypair, batch, staleness_threshold), fields( @@ -624,6 +624,10 @@ where } // Pay priority fees, if configured + #[allow( + clippy::cast_possible_truncation, + reason = "number of instructions won't exceed u32" + )] let total_compute_limit: u32 = compute_unit_limit * instructions.len() as u32; instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( diff --git a/src/agent/state/oracle.rs b/src/agent/state/oracle.rs index eaf2760..de3205d 100644 --- a/src/agent/state/oracle.rs +++ b/src/agent/state/oracle.rs @@ -1,4 +1,4 @@ -#[allow(deprecated)] +#[allow(deprecated, reason = "publishers depend on legacy schedule")] use crate::agent::legacy_schedule::LegacySchedule; use { super::{ @@ -113,6 +113,7 @@ impl PriceEntry { let account_ptr = &*(acc.as_ptr() as *const GenericPriceAccount<0, ()>); let comp_mem = std::slice::from_raw_parts(account_ptr.comp.as_ptr(), size); let mut comp = [PriceComp::default(); 64]; + #[allow(clippy::indexing_slicing, reason = "slice is known to be valid")] comp[0..size].copy_from_slice(comp_mem); Some(Self { account: *account_ptr, @@ -248,7 +249,7 @@ where } let price_entry = PriceEntry::load_from_account(&account.data) - .with_context(|| format!("load price account {}", account_key))?; + .with_context(|| format!("load price account {account_key}"))?; tracing::debug!( pubkey = account_key.to_string(), @@ -433,7 +434,7 @@ type ProductAndPriceAccounts = ( async fn fetch_product_and_price_accounts( rpc_multi_client: &RpcMultiClient, oracle_program_key: Pubkey, - _max_lookup_batch_size: usize, + max_lookup_batch_size: usize, ) -> Result { let mut product_entries = HashMap::new(); let mut price_entries = HashMap::new(); @@ -450,7 +451,7 @@ async fn fetch_product_and_price_accounts( .ok() .map(|product| (pubkey, product)) }) { - #[allow(deprecated)] + #[allow(deprecated, reason = "publishers depend on legacy schedule")] let legacy_schedule: LegacySchedule = if let Some((_wsched_key, wsched_val)) = product.iter().find(|(k, _v)| *k == "weekly_schedule") { @@ -549,7 +550,7 @@ async fn fetch_product_and_price_accounts( )) } -#[allow(dead_code)] +#[allow(dead_code, reason = "may use in the future")] #[instrument(skip(rpc_client, product_key_batch))] async fn fetch_batch_of_product_and_price_accounts( rpc_client: &RpcClient, @@ -566,9 +567,9 @@ async fn fetch_batch_of_product_and_price_accounts( for (product_key, product_account) in product_keys.iter().zip(product_accounts) { if let Some(prod_acc) = product_account { let product = load_product_account(prod_acc.data.as_slice()) - .context(format!("Could not parse product account {}", product_key))?; + .context(format!("Could not parse product account {product_key}"))?; - #[allow(deprecated)] + #[allow(deprecated, reason = "publishers depend on legacy schedule")] let legacy_schedule: LegacySchedule = if let Some((_wsched_key, wsched_val)) = product.iter().find(|(k, _v)| *k == "weekly_schedule") { @@ -671,7 +672,7 @@ async fn fetch_batch_of_product_and_price_accounts( for (price_key, price_account) in todo.iter().zip(price_accounts) { if let Some(price_acc) = price_account { let price = PriceEntry::load_from_account(&price_acc.data) - .context(format!("Could not parse price account at {}", price_key))?; + .context(format!("Could not parse price account at {price_key}"))?; let next_price = Pubkey::from(price.next.to_bytes()); if let Some(prod) = product_entries.get_mut(&Pubkey::from(price.prod.to_bytes())) { From 548e2e9853f434fb087b6a587a3113c8c3d40ff4 Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Mon, 30 Jun 2025 08:10:18 -0500 Subject: [PATCH 2/2] oops, added back unsafe check --- Cargo.toml | 3 +++ src/agent/state/oracle.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a9de51b..3d01b64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,9 @@ panic = 'abort' [profile.dev] panic = 'abort' +[lints.rust] +unsafe_code = "deny" + [lints.clippy] wildcard_dependencies = "deny" diff --git a/src/agent/state/oracle.rs b/src/agent/state/oracle.rs index de3205d..f59a063 100644 --- a/src/agent/state/oracle.rs +++ b/src/agent/state/oracle.rs @@ -86,6 +86,7 @@ pub struct PriceEntry { impl From for PriceEntry { fn from(other: SolanaPriceAccount) -> PriceEntry { + #[allow(unsafe_code, reason = "for tests only")] unsafe { // NOTE: We know the size is 32 because It's a Solana account. This is for tests only. let comp_mem = std::slice::from_raw_parts(other.comp.as_ptr(), 32); @@ -102,6 +103,7 @@ impl PriceEntry { /// Construct the right underlying GenericPriceAccount based on the account size. #[instrument(skip(acc))] pub fn load_from_account(acc: &[u8]) -> Option { + #[allow(unsafe_code, reason = "optimization")] unsafe { let size = match acc.len() { n if n == std::mem::size_of::() => 32,