Skip to content

Commit 68220a8

Browse files
committed
Fix: semaphore rate limiting, integer underflow, unreachable panics, silent error swallowing, division by zero
1 parent 9a40de2 commit 68220a8

4 files changed

Lines changed: 38 additions & 12 deletions

File tree

src/bot.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ impl Bot {
209209
match self.database.get_price_history(crypto_name, 30) {
210210
Ok(history) => {
211211
if history.is_empty() {
212-
let _ = channel_id.say(&ctx.http, "❌ No historical data available yet (waiting for data to be collected)").await;
212+
if let Err(e) = channel_id.say(&ctx.http, "❌ No historical data available yet (waiting for data to be collected)").await {
213+
warn!("Failed to send empty history message: {}", e);
214+
}
213215
return Ok(());
214216
}
215217
match generate_price_chart(&history, crypto_name) {
@@ -305,8 +307,10 @@ impl Bot {
305307
.or_else(|| all_prices.get("PAXG"));
306308

307309
if let Some(gold) = gold_price {
308-
let ratio = gold / current_price;
309-
conversion_prices.push(format!("Ratio: {:.2} (Au/Ag)", ratio));
310+
if current_price > 0.0 {
311+
let ratio = gold / current_price;
312+
conversion_prices.push(format!("Ratio: {:.2} (Au/Ag)", ratio));
313+
}
310314
}
311315
}
312316

@@ -722,7 +726,9 @@ impl EventHandler for Bot {
722726
lines.push("```".to_string());
723727

724728
let message = lines.join("\n");
725-
let _ = msg.channel_id.say(&ctx.http, message).await;
729+
if let Err(e) = msg.channel_id.say(&ctx.http, message).await {
730+
warn!("Failed to send health status message: {}", e);
731+
}
726732
self.health.update_discord_timestamp();
727733
} else {
728734
debug!(
@@ -775,7 +781,10 @@ async fn read_prices_from_file() -> BotResult<PricesFile> {
775781
}
776782
}
777783

778-
unreachable!()
784+
Err(BotError::Io(std::io::Error::new(
785+
std::io::ErrorKind::Other,
786+
"Unexpected error in prices file read retry loop",
787+
)))
779788
}
780789

781790
/// Main price update loop with comprehensive error handling
@@ -1125,7 +1134,11 @@ fn format_custom_status(
11251134
.map(|p| p.price);
11261135

11271136
let ratio_str = if let Some(gold) = gold_price {
1128-
format!("Au/Ag: {:.2}", gold / current_price)
1137+
if current_price > 0.0 {
1138+
format!("Au/Ag: {:.2}", gold / current_price)
1139+
} else {
1140+
format!("{:.8} ₿", btc_amount)
1141+
}
11291142
} else {
11301143
format!("{:.8} ₿", btc_amount) // Fallback
11311144
};
@@ -1395,7 +1408,10 @@ async fn get_individual_crypto_price(feed_id: &str) -> BotResult<f64> {
13951408
}
13961409
}
13971410

1398-
unreachable!()
1411+
Err(BotError::Io(std::io::Error::new(
1412+
std::io::ErrorKind::Other,
1413+
"Unexpected error in prices file read retry loop",
1414+
)))
13991415
}
14001416

14011417
/// Test Discord connectivity by making a simple API call

src/database.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,16 @@ impl PriceDatabase {
163163
];
164164

165165
for (seconds, label) in periods {
166-
let time_ago = current_time - seconds;
166+
// Guard against underflow if clock goes backwards
167+
let time_ago = if current_time >= seconds {
168+
current_time - seconds
169+
} else {
170+
debug!(
171+
"Clock appears to have gone backwards, skipping {} price lookup",
172+
label
173+
);
174+
continue;
175+
};
167176

168177
// Try to get price from appropriate data source based on age
169178
let old_price = if seconds <= 24 * 3600 {

src/discord_api.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ impl DiscordApi {
2727
}
2828

2929
/// Rate-limited Discord API call helper
30+
#[allow(unused_variables)]
3031
async fn rate_limited_call<F, Fut, T>(&self, mut operation: F) -> Result<T, serenity::Error>
3132
where
3233
F: FnMut() -> Fut,
3334
Fut: std::future::Future<Output = Result<T, serenity::Error>>,
3435
{
35-
let _permit = self
36+
let permit = self
3637
.semaphore
3738
.acquire()
3839
.await
@@ -41,7 +42,7 @@ impl DiscordApi {
4142
// Enforce minimum delay between calls
4243
sleep(Duration::from_millis(RATE_LIMIT_DELAY_MS)).await;
4344

44-
// Execute the operation with retry logic
45+
// Execute the operation with retry logic - permit is held during all attempts
4546
for attempt in 1..=MAX_RETRIES {
4647
match operation().await {
4748
Ok(result) => return Ok(result),
@@ -63,7 +64,7 @@ impl DiscordApi {
6364
}
6465
}
6566

66-
unreachable!()
67+
Err(serenity::Error::Other("Rate limiting exhausted"))
6768
}
6869

6970
/// Update bot nickname in a specific guild

src/price_service.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ async fn get_crypto_price(feed_id: &str) -> Result<f64, Box<dyn std::error::Erro
146146
}
147147
}
148148

149-
unreachable!()
149+
Err("Unexpected error in price fetch retry loop".into())
150150
}
151151

152152
pub async fn fetch_shanghai_history(

0 commit comments

Comments
 (0)