1use crate::errors::MigrationError;
7use colored::*;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fs;
11use std::path::Path;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct OracleDetection {
16 pub oracle_type: OracleType,
18 pub confidence: ConfidenceLevel,
20 pub locations: Vec<DetectionLocation>,
22 pub migration_suggestion: String,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum OracleType {
29 Pyth,
31 Switchboard,
33 Chainlink,
35 DIA,
37 RedStone,
39 APRO,
41 Unknown,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum ConfidenceLevel {
48 High,
50 Medium,
52 Low,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct DetectionLocation {
59 pub file_path: String,
61 pub line_number: Option<usize>,
63 pub pattern_matched: String,
65 pub context: String,
67}
68
69#[derive(Debug, Serialize, Deserialize)]
71pub struct OracleReport {
72 pub detected_oracles: Vec<OracleDetection>,
74 pub migration_recommendations: Vec<String>,
76 pub apro_integration_guide: Option<String>,
78}
79
80pub struct OracleDetector;
82
83impl OracleDetector {
84 pub fn scan_project(path: &str, verbose: bool) -> Result<OracleReport, MigrationError> {
105 if verbose {
106 println!("{}", "Scanning project for oracle usage...".cyan());
107 }
108
109 let mut detected_oracles = Vec::new();
110
111 let cargo_detections = Self::scan_cargo_toml(path)?;
113 detected_oracles.extend(cargo_detections);
114
115 let code_detections = Self::scan_rust_files(path)?;
117 detected_oracles.extend(code_detections);
118
119 let merged_detections = Self::merge_detections(detected_oracles);
121
122 let recommendations = Self::generate_recommendations(&merged_detections);
124
125 let apro_guide = if !merged_detections.is_empty() {
127 Some(Self::generate_apro_guide(&merged_detections))
128 } else {
129 None
130 };
131
132 Ok(OracleReport {
133 detected_oracles: merged_detections,
134 migration_recommendations: recommendations,
135 apro_integration_guide: apro_guide,
136 })
137 }
138
139 fn scan_cargo_toml(path: &str) -> Result<Vec<OracleDetection>, MigrationError> {
147 let cargo_path = Path::new(path).join("Cargo.toml");
148 if !cargo_path.exists() {
149 return Ok(Vec::new());
150 }
151
152 let content = fs::read_to_string(&cargo_path)
153 .map_err(|e| MigrationError::OracleDetectionFailed(format!("Failed to read Cargo.toml: {}", e)))?;
154
155 let mut detections = Vec::new();
156
157 if content.contains("pyth-solana-receiver-sdk") {
159 detections.push(OracleDetection {
160 oracle_type: OracleType::Pyth,
161 confidence: ConfidenceLevel::High,
162 locations: vec![DetectionLocation {
163 file_path: "Cargo.toml".to_string(),
164 line_number: Self::find_line_number(&content, "pyth-solana-receiver-sdk"),
165 pattern_matched: "pyth-solana-receiver-sdk".to_string(),
166 context: "Cargo.toml dependency".to_string(),
167 }],
168 migration_suggestion: "Consider migrating to APRO oracle for SOON Network compatibility. APRO provides similar price feed functionality with better performance.".to_string(),
169 });
170 }
171
172 if content.contains("switchboard-v2") || content.contains("switchboard_on_demand") {
174 let pattern = if content.contains("switchboard-v2") { "switchboard-v2" } else { "switchboard_on_demand" };
175 detections.push(OracleDetection {
176 oracle_type: OracleType::Switchboard,
177 confidence: ConfidenceLevel::High,
178 locations: vec![DetectionLocation {
179 file_path: "Cargo.toml".to_string(),
180 line_number: Self::find_line_number(&content, pattern),
181 pattern_matched: pattern.to_string(),
182 context: "Cargo.toml dependency".to_string(),
183 }],
184 migration_suggestion: "APRO oracle offers customizable data feeds similar to Switchboard with enhanced performance on SOON Network.".to_string(),
185 });
186 }
187
188 if content.contains("chainlink_solana") {
190 detections.push(OracleDetection {
191 oracle_type: OracleType::Chainlink,
192 confidence: ConfidenceLevel::High,
193 locations: vec![DetectionLocation {
194 file_path: "Cargo.toml".to_string(),
195 line_number: Self::find_line_number(&content, "chainlink_solana"),
196 pattern_matched: "chainlink_solana".to_string(),
197 context: "Cargo.toml dependency".to_string(),
198 }],
199 migration_suggestion: "APRO oracle provides reliable price feeds compatible with Chainlink's API patterns for seamless migration.".to_string(),
200 });
201 }
202
203 Ok(detections)
204 }
205
206 fn scan_rust_files(path: &str) -> Result<Vec<OracleDetection>, MigrationError> {
214 let mut detections = Vec::new();
215
216 Self::walk_directory(Path::new(path), &mut |file_path| {
218 if let Some(extension) = file_path.extension() {
219 if extension == "rs" {
220 if let Ok(content) = fs::read_to_string(file_path) {
221 detections.extend(Self::scan_rust_content(&content, file_path)?);
222 }
223 }
224 }
225 Ok(())
226 })?;
227
228 Ok(detections)
229 }
230
231 fn scan_rust_content(content: &str, file_path: &Path) -> Result<Vec<OracleDetection>, MigrationError> {
240 let mut detections = Vec::new();
241 let file_path_str = file_path.to_string_lossy().to_string();
242
243 let pyth_patterns = [
245 "use pyth_solana_receiver_sdk",
246 "PriceUpdateV2",
247 "get_price_no_older_than",
248 "pyth_solana_receiver_sdk::",
249 ];
250
251 for pattern in &pyth_patterns {
252 if content.contains(pattern) {
253 detections.push(OracleDetection {
254 oracle_type: OracleType::Pyth,
255 confidence: ConfidenceLevel::High,
256 locations: vec![DetectionLocation {
257 file_path: file_path_str.clone(),
258 line_number: Self::find_line_number(content, pattern),
259 pattern_matched: pattern.to_string(),
260 context: format!("Rust code usage: {}", pattern),
261 }],
262 migration_suggestion: "Replace Pyth price feeds with APRO oracle integration. See APRO documentation for migration guide.".to_string(),
263 });
264 break; }
266 }
267
268 let switchboard_patterns = [
270 "use switchboard_v2",
271 "use switchboard_on_demand",
272 "AggregatorAccountData",
273 "get_result",
274 "switchboard_v2::",
275 ];
276
277 for pattern in &switchboard_patterns {
278 if content.contains(pattern) {
279 detections.push(OracleDetection {
280 oracle_type: OracleType::Switchboard,
281 confidence: ConfidenceLevel::High,
282 locations: vec![DetectionLocation {
283 file_path: file_path_str.clone(),
284 line_number: Self::find_line_number(content, pattern),
285 pattern_matched: pattern.to_string(),
286 context: format!("Rust code usage: {}", pattern),
287 }],
288 migration_suggestion: "Replace Switchboard aggregators with APRO oracle data feeds for SOON Network compatibility.".to_string(),
289 });
290 break;
291 }
292 }
293
294 let chainlink_patterns = [
296 "use chainlink_solana",
297 "latest_round_data",
298 "chainlink_solana::",
299 "chainlink::",
300 ];
301
302 for pattern in &chainlink_patterns {
303 if content.contains(pattern) {
304 detections.push(OracleDetection {
305 oracle_type: OracleType::Chainlink,
306 confidence: ConfidenceLevel::High,
307 locations: vec![DetectionLocation {
308 file_path: file_path_str.clone(),
309 line_number: Self::find_line_number(content, pattern),
310 pattern_matched: pattern.to_string(),
311 context: format!("Rust code usage: {}", pattern),
312 }],
313 migration_suggestion: "Migrate Chainlink price feeds to APRO oracle for enhanced performance on SOON Network.".to_string(),
314 });
315 break;
316 }
317 }
318
319 let dia_patterns = [
321 "CoinInfo",
322 "// DIA",
323 "dia oracle",
324 "DIA Oracle",
325 ];
326
327 for pattern in &dia_patterns {
328 if content.to_lowercase().contains(&pattern.to_lowercase()) {
329 detections.push(OracleDetection {
330 oracle_type: OracleType::DIA,
331 confidence: ConfidenceLevel::Low,
332 locations: vec![DetectionLocation {
333 file_path: file_path_str.clone(),
334 line_number: Self::find_line_number(content, pattern),
335 pattern_matched: pattern.to_string(),
336 context: format!("Potential DIA usage: {}", pattern),
337 }],
338 migration_suggestion: "If using DIA oracle, consider migrating to APRO for better SOON Network integration.".to_string(),
339 });
340 break;
341 }
342 }
343
344 let redstone_patterns = [
346 "redstone",
347 "RedStone",
348 "// RedStone",
349 "wormhole",
350 ];
351
352 for pattern in &redstone_patterns {
353 if content.contains(pattern) {
354 detections.push(OracleDetection {
355 oracle_type: OracleType::RedStone,
356 confidence: ConfidenceLevel::Low,
357 locations: vec![DetectionLocation {
358 file_path: file_path_str.clone(),
359 line_number: Self::find_line_number(content, pattern),
360 pattern_matched: pattern.to_string(),
361 context: format!("Potential RedStone usage: {}", pattern),
362 }],
363 migration_suggestion: "If using RedStone oracle, APRO provides similar RWA oracle capabilities for SOON Network.".to_string(),
364 });
365 break;
366 }
367 }
368
369 Ok(detections)
370 }
371
372 fn walk_directory<F>(dir: &Path, callback: &mut F) -> Result<(), MigrationError>
381 where
382 F: FnMut(&Path) -> Result<(), MigrationError>,
383 {
384 if dir.is_dir() {
385 let entries = fs::read_dir(dir)
386 .map_err(|e| MigrationError::OracleDetectionFailed(format!("Failed to read directory: {}", e)))?;
387
388 for entry in entries {
389 let entry = entry
390 .map_err(|e| MigrationError::OracleDetectionFailed(format!("Failed to read directory entry: {}", e)))?;
391 let path = entry.path();
392
393 if path.is_dir() {
394 if let Some(dir_name) = path.file_name() {
396 let dir_str = dir_name.to_string_lossy();
397 if dir_str == "target" || dir_str == "node_modules" || dir_str == ".git" {
398 continue;
399 }
400 }
401 Self::walk_directory(&path, callback)?;
402 } else {
403 callback(&path)?;
404 }
405 }
406 }
407 Ok(())
408 }
409
410 fn find_line_number(content: &str, pattern: &str) -> Option<usize> {
419 content
420 .lines()
421 .enumerate()
422 .find(|(_, line)| line.contains(pattern))
423 .map(|(i, _)| i + 1)
424 }
425
426 fn merge_detections(detections: Vec<OracleDetection>) -> Vec<OracleDetection> {
434 let mut oracle_map: HashMap<String, OracleDetection> = HashMap::new();
435
436 for detection in detections {
437 let key = format!("{:?}", detection.oracle_type);
438
439 if let Some(existing) = oracle_map.get_mut(&key) {
440 existing.locations.extend(detection.locations);
442 if matches!(detection.confidence, ConfidenceLevel::High) {
444 existing.confidence = ConfidenceLevel::High;
445 } else if matches!(detection.confidence, ConfidenceLevel::Medium) && matches!(existing.confidence, ConfidenceLevel::Low) {
446 existing.confidence = ConfidenceLevel::Medium;
447 }
448 } else {
449 oracle_map.insert(key, detection);
450 }
451 }
452
453 oracle_map.into_values().collect()
454 }
455
456 fn generate_recommendations(detections: &[OracleDetection]) -> Vec<String> {
464 let mut recommendations = Vec::new();
465
466 if detections.is_empty() {
467 recommendations.push("No oracle usage detected. Your project should migrate smoothly to SOON Network.".to_string());
468 return recommendations;
469 }
470
471 recommendations.push("🔍 Oracle usage detected in your project. Consider these migration steps:".to_string());
472 recommendations.push("".to_string());
473
474 for detection in detections {
475 let confidence_icon = match detection.confidence {
476 ConfidenceLevel::High => "🔴",
477 ConfidenceLevel::Medium => "🟡",
478 ConfidenceLevel::Low => "🟢",
479 };
480
481 recommendations.push(format!("{} {:?} Oracle detected with {} confidence",
482 confidence_icon, detection.oracle_type,
483 format!("{:?}", detection.confidence).to_lowercase()));
484
485 for location in &detection.locations {
486 if let Some(line_num) = location.line_number {
487 recommendations.push(format!(" 📁 {}:{} - {}", location.file_path, line_num, location.pattern_matched));
488 } else {
489 recommendations.push(format!(" 📁 {} - {}", location.file_path, location.pattern_matched));
490 }
491 }
492 recommendations.push(format!(" 💡 {}", detection.migration_suggestion));
493 recommendations.push("".to_string());
494 }
495
496 recommendations.push("📚 Next steps:".to_string());
497 recommendations.push("1. Review the APRO oracle integration guide generated below".to_string());
498 recommendations.push("2. Update your dependencies to use APRO oracle SDK".to_string());
499 recommendations.push("3. Replace oracle-specific code with APRO equivalents".to_string());
500 recommendations.push("4. Test your price feed integrations on SOON devnet".to_string());
501
502 recommendations
503 }
504
505 fn generate_apro_guide(detections: &[OracleDetection]) -> String {
513 let mut guide = String::new();
514
515 guide.push_str("# APRO Oracle Integration Guide for SOON Network\n\n");
516 guide.push_str("## Overview\n");
517 guide.push_str("APRO has chosen SOON as their first SVM chain to support oracle services. ");
518 guide.push_str("This guide will help you migrate your existing oracle integrations.\n\n");
519
520 guide.push_str("## Program IDs\n");
521 guide.push_str("```\n");
522 guide.push_str("Devnet: 4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY\n");
523 guide.push_str("Mainnet: 4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY\n");
524 guide.push_str("```\n\n");
525
526 guide.push_str("## API Endpoints\n");
527 guide.push_str("```\n");
528 guide.push_str("Devnet: https://live-api-test.apro.com\n");
529 guide.push_str("Mainnet: https://live-api.apro.com\n");
530 guide.push_str("```\n\n");
531
532 for detection in detections {
534 match detection.oracle_type {
535 OracleType::Pyth => {
536 guide.push_str("## Migrating from Pyth\n");
537 guide.push_str("Replace your Pyth price feed calls:\n");
538 guide.push_str("```rust\n");
539 guide.push_str("// Before (Pyth)\n");
540 guide.push_str("use pyth_solana_receiver_sdk::PriceUpdateV2;\n");
541 guide.push_str("let price = price_update.get_price_no_older_than(&clock, max_age)?;\n\n");
542 guide.push_str("// After (APRO)\n");
543 guide.push_str("use oracle_sdk::load_price_feed_from_account_info;\n");
544 guide.push_str("let price_feed = load_price_feed_from_account_info(&price_account)?;\n");
545 guide.push_str("let price = price_feed.benchmark_price;\n");
546 guide.push_str("```\n\n");
547 }
548 OracleType::Switchboard => {
549 guide.push_str("## Migrating from Switchboard\n");
550 guide.push_str("Replace your Switchboard aggregator calls:\n");
551 guide.push_str("```rust\n");
552 guide.push_str("// Before (Switchboard)\n");
553 guide.push_str("use switchboard_v2::AggregatorAccountData;\n");
554 guide.push_str("let result = aggregator.get_result()?;\n\n");
555 guide.push_str("// After (APRO)\n");
556 guide.push_str("use oracle_sdk::load_price_feed_from_account_info;\n");
557 guide.push_str("let price_feed = load_price_feed_from_account_info(&price_account)?;\n");
558 guide.push_str("let result = price_feed.benchmark_price;\n");
559 guide.push_str("```\n\n");
560 }
561 OracleType::Chainlink => {
562 guide.push_str("## Migrating from Chainlink\n");
563 guide.push_str("Replace your Chainlink price feed calls:\n");
564 guide.push_str("```rust\n");
565 guide.push_str("// Before (Chainlink)\n");
566 guide.push_str("use chainlink_solana as chainlink;\n");
567 guide.push_str("let round_data = chainlink::latest_round_data(ctx, &feed_account)?;\n\n");
568 guide.push_str("// After (APRO)\n");
569 guide.push_str("use oracle_sdk::load_price_feed_from_account_info;\n");
570 guide.push_str("let price_feed = load_price_feed_from_account_info(&price_account)?;\n");
571 guide.push_str("let price = price_feed.benchmark_price;\n");
572 guide.push_str("```\n\n");
573 }
574 _ => {}
575 }
576 }
577
578 guide.push_str("## Available Price Feeds (Devnet)\n");
579 guide.push_str("- BTC/USD: 0x0003665949c883f9e0f6f002eac32e00bd59dfe6c34e92a91c37d6a8322d6489\n");
580 guide.push_str("- ETH/USD: 0x0003555ace6b39aae1b894097d0a9fc17f504c62fea598fa206cc6f5088e6e45\n");
581 guide.push_str("- SOL/USD: 0x000343ec7f6691d6bf679978bab5c093fa45ee74c0baac6cc75649dc59cc21d3\n");
582 guide.push_str("- USDT/USD: 0x00039a0c0be4e43cacda1599ac414205651f4a62b614b6be9e5318a182c33eb0\n");
583 guide.push_str("- USDC/USD: 0x00034b881a0c0fff844177f881a313ff894bfc6093d33b5514e34d7faa41b7ef\n\n");
584
585 guide.push_str("## Getting Started\n");
586 guide.push_str("1. Contact APRO BD team for authorization:\n");
587 guide.push_str(" - Email: bd@apro.com\n");
588 guide.push_str(" - Telegram: Head of Business Development\n");
589 guide.push_str("2. Add APRO oracle SDK to your Cargo.toml\n");
590 guide.push_str("3. Update your price feed integration code\n");
591 guide.push_str("4. Test on SOON devnet before mainnet deployment\n\n");
592
593 guide.push_str("For detailed integration examples, see the complete APRO documentation.\n");
594
595 guide
596 }
597
598 pub fn print_report(report: &OracleReport, verbose: bool) {
604 println!("{}", "=== Oracle Detection Report ===".bold().cyan());
605 println!();
606
607 if report.detected_oracles.is_empty() {
608 println!("{} No oracle usage detected", "✅".green());
609 println!("Your project should migrate smoothly to SOON Network without oracle changes.");
610 return;
611 }
612
613 println!("{} {} oracle(s) detected:", "🔍".yellow(), report.detected_oracles.len());
614 println!();
615
616 for detection in &report.detected_oracles {
617 let confidence_color = match detection.confidence {
618 ConfidenceLevel::High => "red",
619 ConfidenceLevel::Medium => "yellow",
620 ConfidenceLevel::Low => "green",
621 };
622
623 println!("{} {:?} Oracle ({})",
624 match detection.confidence {
625 ConfidenceLevel::High => "🔴",
626 ConfidenceLevel::Medium => "🟡",
627 ConfidenceLevel::Low => "🟢",
628 },
629 detection.oracle_type,
630 format!("{:?} confidence", detection.confidence).color(confidence_color)
631 );
632
633 if verbose {
634 for location in &detection.locations {
635 if let Some(line) = location.line_number {
636 println!(" 📁 {}:{} - {}", location.file_path, line, location.pattern_matched);
637 } else {
638 println!(" 📁 {} - {}", location.file_path, location.pattern_matched);
639 }
640 }
641 }
642
643 println!(" 💡 {}", detection.migration_suggestion);
644 println!();
645 }
646
647 println!("{}", "=== Migration Recommendations ===".bold().cyan());
648 for rec in &report.migration_recommendations {
649 if rec.starts_with("🔍") || rec.starts_with("📚") {
650 println!("{}", rec.bold());
651 } else if rec.is_empty() {
652 println!();
653 } else {
654 println!("{}", rec);
655 }
656 }
657
658 if report.apro_integration_guide.is_some() {
659 println!();
660 println!("{}", "💡 Run with --show-guide to see the complete APRO integration guide".yellow());
661 }
662 }
663
664 pub fn print_integration_guide(report: &OracleReport) {
669 if let Some(guide) = &report.apro_integration_guide {
670 println!("{}", guide);
671 } else {
672 println!("No oracle integration guide available.");
673 }
674 }
675}
676
677impl std::fmt::Display for OracleType {
678 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679 match self {
680 OracleType::Pyth => write!(f, "Pyth"),
681 OracleType::Switchboard => write!(f, "Switchboard"),
682 OracleType::Chainlink => write!(f, "Chainlink"),
683 OracleType::DIA => write!(f, "DIA"),
684 OracleType::RedStone => write!(f, "RedStone"),
685 OracleType::APRO => write!(f, "APRO"),
686 OracleType::Unknown => write!(f, "Unknown"),
687 }
688 }
689}