1use crate::cli::output::Output;
8use crate::outln;
9use clap::Args;
10use scope::chains::ChainClientFactory;
11use scope::config::Config;
12use scope::contract;
13use scope::error::Result;
14
15#[derive(Debug, Args)]
17#[command(
18 after_help = "\x1b[1mExamples:\x1b[0m
19 scope contract 0xdAC17F958D2ee523a2206206994597C13D831ec7
20 scope ct @usdt-contract \x1b[2m# address book shortcut\x1b[0m
21 scope ct 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --chain polygon
22 scope contract 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --json",
23 after_long_help = "\x1b[1mExamples:\x1b[0m
24
25 \x1b[1m$ scope contract 0xdAC17F958D2ee523a2206206994597C13D831ec7\x1b[0m
26
27 ┌─ Contract Analysis: 0xdAC17F958D2ee523a2206206994597C13D831ec7 ─
28 │ Chain ethereum
29 │ Verified Yes
30 │
31 │ Security Score [████████████████────] 80/100
32 │
33 ├── Source Code
34 │ Contract Name TetherToken
35 │ Compiler v0.4.18+commit.9cf6e910
36 │ Optimization No
37 │
38 ├── Proxy Detection
39 │ ✓ Not a proxy contract
40 │
41 ├── Access Control
42 │ Ownership Ownable
43 │ Renounced No
44 │ • pause (High): Can pause transfers
45 │ • addBlacklist (High): Can blacklist addresses
46 │
47 ├── Vulnerability Findings
48 │ ℹ SC-TX-ORIGIN — tx.origin authorization (Low)
49 │
50 ├── DeFi Analysis
51 │ Protocol Type Token
52 │ Token Standards ERC-20
53 │
54 ├── External Intelligence
55 │ Explorer https://etherscan.io/address/0xdAC17...
56 │ ✓ Sourcify verified
57 │ • Trail of Bits (TetherToken)
58 └──────────────────────────────────────────────────
59
60 \x1b[1m$ scope ct 0xA0b86991... --json\x1b[0m
61
62 {
63 \"address\": \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",
64 \"chain\": \"ethereum\",
65 \"is_verified\": true,
66 \"security_score\": 85,
67 \"security_summary\": \"Verified contract with ...\",
68 \"source_info\": { ... },
69 \"proxy_info\": { ... },
70 \"vulnerabilities\": [ ... ],
71 ...
72 }"
73)]
74pub struct ContractArgs {
75 #[arg(value_name = "ADDRESS")]
81 pub address: String,
82
83 #[arg(
84 long,
85 short,
86 default_value = scope::chains::DEFAULT_CHAIN,
87 help = format!("Target blockchain network (Etherscan-compatible API): {}", scope::chains::registry::explorer_api_chain_list())
88 )]
89 pub chain: String,
90
91 #[arg(long)]
95 pub json: bool,
96}
97
98pub async fn run(
100 args: &ContractArgs,
101 _config: &Config,
102 clients: &dyn ChainClientFactory,
103 out: &Output,
104) -> Result<()> {
105 let spinner = crate::cli::progress::Spinner::new("Analyzing contract...", out);
106
107 let client = clients.create_chain_client(&args.chain)?;
108 let http_client = clients.http();
109
110 let analysis = contract::analyze_contract(
111 &args.address,
112 &args.chain,
113 client.as_ref(),
114 http_client.as_ref(),
115 )
116 .await?;
117
118 spinner.finish("Contract analysis complete");
119
120 if args.json {
121 outln!(
122 out,
123 "{}",
124 serde_json::to_string_pretty(&analysis)
125 .unwrap_or_else(|_| "Failed to serialize".to_string())
126 )?;
127 } else {
128 print_contract_report(&analysis, out)?;
129 }
130
131 Ok(())
132}
133
134fn print_contract_report(
139 analysis: &contract::ContractAnalysis,
140 out: &Output,
141) -> std::io::Result<()> {
142 use scope::display::terminal as t;
143
144 let title = format!("Contract Analysis: {}", analysis.address);
145 outln!(out, "{}", t::section_header(&title))?;
146 outln!(out, "{}", t::kv_row("Chain", &analysis.chain))?;
147 outln!(
148 out,
149 "{}",
150 t::kv_row("Verified", if analysis.is_verified { "Yes" } else { "No" })
151 )?;
152 outln!(out, "{}", t::blank_row())?;
153 outln!(
154 out,
155 "{}",
156 t::score_bar("Security Score", analysis.security_score, 100)
157 )?;
158 outln!(out, "{}", t::detail_row(&analysis.security_summary))?;
159
160 if !analysis.is_verified {
161 outln!(out, "{}", t::blank_row())?;
162 outln!(
163 out,
164 "{}",
165 t::warning_row("Source code is NOT verified — analysis is limited")
166 )?;
167 }
168
169 if let Some(src) = &analysis.source_info {
171 outln!(out, "{}", t::subsection_header("Source Code"))?;
172 outln!(out, "{}", t::kv_row("Contract Name", &src.contract_name))?;
173 outln!(out, "{}", t::kv_row("Compiler", &src.compiler_version))?;
174 outln!(out, "{}", t::kv_row("EVM Version", &src.evm_version))?;
175 outln!(out, "{}", t::kv_row("License", &src.license_type))?;
176 outln!(
177 out,
178 "{}",
179 t::kv_row(
180 "Optimization",
181 &if src.optimization_used {
182 format!("Yes ({} runs)", src.optimization_runs)
183 } else {
184 "No".to_string()
185 }
186 )
187 )?;
188 outln!(
189 out,
190 "{}",
191 t::kv_row("ABI Functions", &src.parsed_abi.len().to_string())
192 )?;
193 }
194
195 if let Some(proxy) = &analysis.proxy_info {
197 outln!(out, "{}", t::subsection_header("Proxy Detection"))?;
198 if proxy.is_proxy {
199 outln!(out, "{}", t::kv_row("Type", &proxy.proxy_type))?;
200 if let Some(impl_addr) = &proxy.implementation_address {
201 outln!(out, "{}", t::kv_row("Implementation", impl_addr))?;
202 }
203 if let Some(admin) = &proxy.admin_address {
204 outln!(out, "{}", t::kv_row("Admin", admin))?;
205 }
206 } else {
207 outln!(out, "{}", t::check_pass("Not a proxy contract"))?;
208 }
209 for detail in &proxy.details {
210 outln!(out, "{}", t::bullet_row(detail))?;
211 }
212 }
213
214 if let Some(ac) = &analysis.access_control {
216 outln!(out, "{}", t::subsection_header("Access Control"))?;
217 if let Some(pattern) = &ac.ownership_pattern {
218 outln!(out, "{}", t::kv_row("Ownership", pattern))?;
219 }
220 outln!(
221 out,
222 "{}",
223 t::kv_row(
224 "Renounced",
225 if ac.has_renounced_ownership {
226 "Yes"
227 } else {
228 "No"
229 }
230 )
231 )?;
232 outln!(
233 out,
234 "{}",
235 t::kv_row(
236 "Role-based",
237 if ac.has_role_based_access {
238 "Yes"
239 } else {
240 "No"
241 }
242 )
243 )?;
244 if ac.uses_tx_origin {
245 outln!(
246 out,
247 "{}",
248 t::warning_row("Uses tx.origin for authorization")
249 )?;
250 }
251 if !ac.roles.is_empty() {
252 outln!(out, "{}", t::kv_row("Roles", &ac.roles.join(", ")))?;
253 }
254 if !ac.privileged_functions.is_empty() {
255 outln!(out, "{}", t::blank_row())?;
256 for pf in &ac.privileged_functions {
257 let sev = t::severity_label(&format!("{:?}", pf.risk));
258 outln!(
259 out,
260 "{}",
261 t::bullet_row(&format!("{} ({}): {}", pf.name, sev, pf.capability))
262 )?;
263 }
264 }
265 outln!(out, "{}", t::blank_row())?;
266 outln!(out, "{}", t::kv_row("Auth", &ac.auth_analysis.summary))?;
267 }
268
269 outln!(out, "{}", t::subsection_header("Vulnerability Findings"))?;
271 if !analysis.vulnerabilities.is_empty() {
272 for vuln in &analysis.vulnerabilities {
273 let sev_str = format!("{}", vuln.severity);
274 let sev = t::severity_label(&sev_str);
275 match vuln.severity {
276 contract::vulnerability::Severity::Critical
277 | contract::vulnerability::Severity::High => {
278 outln!(
279 out,
280 "{}",
281 t::check_fail(&format!("{} — {} ({})", vuln.id, vuln.title, sev))
282 )?;
283 }
284 _ => {
285 outln!(
286 out,
287 "{}",
288 t::info_row(&format!("{} — {} ({})", vuln.id, vuln.title, sev))
289 )?;
290 }
291 }
292 outln!(out, "{}", t::detail_row(&vuln.description))?;
293 outln!(
294 out,
295 "{}",
296 t::detail_row(&format!("Fix: {}", vuln.recommendation))
297 )?;
298 }
299 } else {
300 outln!(out, "{}", t::check_pass("No heuristic findings triggered"))?;
301 }
302
303 if let Some(defi) = &analysis.defi_analysis {
305 outln!(out, "{}", t::subsection_header("DeFi Analysis"))?;
306 outln!(
307 out,
308 "{}",
309 t::kv_row("Protocol Type", &defi.protocol_type.to_string())
310 )?;
311 if !defi.token_standards.is_empty() {
312 let standards: Vec<String> =
313 defi.token_standards.iter().map(|s| s.to_string()).collect();
314 outln!(
315 out,
316 "{}",
317 t::kv_row("Token Standards", &standards.join(", "))
318 )?;
319 }
320 if defi.has_oracle_dependency {
321 for oracle in &defi.oracle_info {
322 outln!(
323 out,
324 "{}",
325 t::kv_row("Oracle", &format!("{} ({})", oracle.provider, oracle.usage))
326 )?;
327 }
328 }
329 if defi.has_flash_loan_risk {
330 outln!(out, "{}", t::warning_row("Flash loan risk detected"))?;
331 }
332 for dex in &defi.dex_integrations {
333 let slippage = if dex.has_slippage_protection {
334 "✓"
335 } else {
336 "✗"
337 };
338 let deadline = if dex.has_deadline_protection {
339 "✓"
340 } else {
341 "✗"
342 };
343 outln!(
344 out,
345 "{}",
346 t::bullet_row(&format!(
347 "{} — slippage: {} deadline: {}",
348 dex.dex, slippage, deadline
349 ))
350 )?;
351 }
352 if !defi.risk_factors.is_empty() {
353 outln!(out, "{}", t::blank_row())?;
354 for rf in &defi.risk_factors {
355 outln!(
356 out,
357 "{}",
358 t::bullet_row(&format!(
359 "{} ({}/10): {}",
360 rf.name, rf.severity, rf.description
361 ))
362 )?;
363 }
364 }
365 }
366
367 if let Some(ext) = &analysis.external_info {
369 outln!(out, "{}", t::subsection_header("External Intelligence"))?;
370 outln!(out, "{}", t::link_row("Explorer", &ext.explorer_url))?;
371 if let Some(repo) = &ext.github_repo {
372 outln!(out, "{}", t::link_row("GitHub", repo))?;
373 }
374 if let Some(verified) = &ext.sourcify_verified {
375 if *verified {
376 outln!(out, "{}", t::check_pass("Sourcify verified"))?;
377 } else {
378 outln!(out, "{}", t::check_fail("Sourcify not verified"))?;
379 }
380 }
381 if !ext.audit_reports.is_empty() {
382 outln!(out, "{}", t::blank_row())?;
383 for report in &ext.audit_reports {
384 outln!(
385 out,
386 "{}",
387 t::bullet_row(&format!("{} ({})", report.auditor, report.scope))
388 )?;
389 if !report.url.is_empty() {
390 outln!(out, "{}", t::detail_row(&report.url))?;
391 }
392 }
393 } else {
394 outln!(out, "{}", t::blank_row())?;
395 outln!(
396 out,
397 "{}",
398 t::info_row("No audit reports found — check block explorer manually")
399 )?;
400 }
401 }
402
403 outln!(out, "{}", t::section_footer())?;
404 Ok(())
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use scope::contract::ContractAnalysis;
411
412 fn quiet() -> crate::cli::output::Output {
413 crate::cli::output::Output::capture().0
414 }
415
416 fn minimal_analysis() -> ContractAnalysis {
417 ContractAnalysis {
418 address: "0xtest".to_string(),
419 chain: "ethereum".to_string(),
420 is_verified: false,
421 source_info: None,
422 proxy_info: None,
423 access_control: None,
424 vulnerabilities: vec![],
425 defi_analysis: None,
426 external_info: None,
427 security_score: 30,
428 security_summary: "Unverified contract".to_string(),
429 }
430 }
431
432 #[test]
433 fn test_print_report_minimal() {
434 print_contract_report(&minimal_analysis(), &quiet()).unwrap();
435 }
436
437 #[test]
438 fn test_print_report_verified_with_source() {
439 let mut a = minimal_analysis();
440 a.is_verified = true;
441 a.security_score = 75;
442 a.source_info = Some(scope::contract::source::ContractSource {
443 contract_name: "TestToken".to_string(),
444 source_code: "contract T {}".to_string(),
445 abi: "[]".to_string(),
446 compiler_version: "v0.8.19".to_string(),
447 optimization_used: true,
448 optimization_runs: 200,
449 evm_version: "paris".to_string(),
450 license_type: "MIT".to_string(),
451 is_proxy: false,
452 implementation_address: None,
453 constructor_arguments: String::new(),
454 library: String::new(),
455 swarm_source: String::new(),
456 parsed_abi: vec![],
457 });
458 print_contract_report(&a, &quiet()).unwrap();
459 }
460
461 #[test]
462 fn test_print_report_source_no_optimization() {
463 let mut a = minimal_analysis();
464 a.is_verified = true;
465 a.source_info = Some(scope::contract::source::ContractSource {
466 contract_name: "T".to_string(),
467 source_code: String::new(),
468 abi: "[]".to_string(),
469 compiler_version: "v0.8.19".to_string(),
470 optimization_used: false,
471 optimization_runs: 0,
472 evm_version: "paris".to_string(),
473 license_type: "MIT".to_string(),
474 is_proxy: false,
475 implementation_address: None,
476 constructor_arguments: String::new(),
477 library: String::new(),
478 swarm_source: String::new(),
479 parsed_abi: vec![],
480 });
481 print_contract_report(&a, &quiet()).unwrap();
482 }
483
484 #[test]
485 fn test_print_report_with_proxy() {
486 let mut a = minimal_analysis();
487 a.proxy_info = Some(scope::contract::proxy::ProxyInfo {
488 is_proxy: true,
489 proxy_type: "EIP-1967".to_string(),
490 implementation_address: Some("0ximpl".to_string()),
491 admin_address: Some("0xadmin".to_string()),
492 beacon_address: None,
493 details: vec!["Proxy detected".to_string()],
494 });
495 print_contract_report(&a, &quiet()).unwrap();
496 }
497
498 #[test]
499 fn test_print_report_not_proxy() {
500 let mut a = minimal_analysis();
501 a.proxy_info = Some(scope::contract::proxy::ProxyInfo {
502 is_proxy: false,
503 proxy_type: "None".to_string(),
504 implementation_address: None,
505 admin_address: None,
506 beacon_address: None,
507 details: vec![],
508 });
509 print_contract_report(&a, &quiet()).unwrap();
510 }
511
512 #[test]
513 fn test_print_report_access_control() {
514 let mut a = minimal_analysis();
515 a.access_control = Some(scope::contract::access::AccessControlMap {
516 ownership_pattern: Some("Ownable".to_string()),
517 has_renounced_ownership: true,
518 has_role_based_access: true,
519 uses_tx_origin: true,
520 tx_origin_locations: vec![],
521 modifiers: vec![],
522 privileged_functions: vec![scope::contract::access::PrivilegedFunction {
523 name: "mint".to_string(),
524 modifiers: vec!["onlyOwner".to_string()],
525 capability: "Mint tokens".to_string(),
526 risk: scope::contract::access::PrivilegeRisk::Critical,
527 }],
528 roles: vec!["MINTER_ROLE".to_string()],
529 auth_analysis: scope::contract::access::AuthAnalysis {
530 msg_sender_checks: 1,
531 tx_origin_checks: 1,
532 has_origin_sender_comparison: false,
533 summary: "Mixed auth".to_string(),
534 },
535 });
536 print_contract_report(&a, &quiet()).unwrap();
537 }
538
539 #[test]
540 fn test_print_report_vulns() {
541 let mut a = minimal_analysis();
542 a.vulnerabilities = vec![
543 contract::vulnerability::VulnerabilityFinding {
544 id: "V-1".to_string(),
545 title: "Critical issue".to_string(),
546 severity: contract::vulnerability::Severity::Critical,
547 category: contract::vulnerability::VulnCategory::Reentrancy,
548 description: "desc".to_string(),
549 source_location: None,
550 recommendation: "fix".to_string(),
551 },
552 contract::vulnerability::VulnerabilityFinding {
553 id: "V-2".to_string(),
554 title: "High issue".to_string(),
555 severity: contract::vulnerability::Severity::High,
556 category: contract::vulnerability::VulnCategory::UncheckedCall,
557 description: "desc".to_string(),
558 source_location: None,
559 recommendation: "fix".to_string(),
560 },
561 contract::vulnerability::VulnerabilityFinding {
562 id: "V-3".to_string(),
563 title: "Medium".to_string(),
564 severity: contract::vulnerability::Severity::Medium,
565 category: contract::vulnerability::VulnCategory::Delegatecall,
566 description: "desc".to_string(),
567 source_location: None,
568 recommendation: "fix".to_string(),
569 },
570 contract::vulnerability::VulnerabilityFinding {
571 id: "V-4".to_string(),
572 title: "Low".to_string(),
573 severity: contract::vulnerability::Severity::Low,
574 category: contract::vulnerability::VulnCategory::TxOrigin,
575 description: "desc".to_string(),
576 source_location: None,
577 recommendation: "fix".to_string(),
578 },
579 contract::vulnerability::VulnerabilityFinding {
580 id: "V-5".to_string(),
581 title: "Info".to_string(),
582 severity: contract::vulnerability::Severity::Informational,
583 category: contract::vulnerability::VulnCategory::Informational,
584 description: "desc".to_string(),
585 source_location: None,
586 recommendation: "fix".to_string(),
587 },
588 ];
589 print_contract_report(&a, &quiet()).unwrap();
590 }
591
592 #[test]
593 fn test_print_report_defi() {
594 let mut a = minimal_analysis();
595 a.defi_analysis = Some(scope::contract::defi::DefiAnalysis {
596 protocol_type: scope::contract::defi::ProtocolType::DEX,
597 has_oracle_dependency: true,
598 oracle_info: vec![scope::contract::defi::OracleInfo {
599 provider: "Chainlink".to_string(),
600 usage: "Price feed".to_string(),
601 risks: vec![],
602 }],
603 has_flash_loan_risk: true,
604 flash_loan_info: vec!["Flash loan detected".to_string()],
605 dex_integrations: vec![scope::contract::defi::DexIntegration {
606 dex: "Uniswap".to_string(),
607 integration_type: "Swap".to_string(),
608 has_slippage_protection: false,
609 has_deadline_protection: true,
610 }],
611 lending_patterns: vec![],
612 token_standards: vec![scope::contract::defi::TokenStandard::ERC20],
613 staking_patterns: vec![],
614 risk_factors: vec![scope::contract::defi::DefiRiskFactor {
615 name: "Test risk".to_string(),
616 description: "A risk".to_string(),
617 severity: 7,
618 }],
619 });
620 print_contract_report(&a, &quiet()).unwrap();
621 }
622
623 #[test]
624 fn test_print_report_external() {
625 let mut a = minimal_analysis();
626 a.external_info = Some(scope::contract::external::ExternalInfo {
627 explorer_url: "https://etherscan.io/address/0xtest".to_string(),
628 github_repo: Some("https://github.com/test/repo".to_string()),
629 sourcify_verified: Some(true),
630 deployer: None,
631 audit_reports: vec![scope::contract::external::AuditReport {
632 auditor: "Trail of Bits".to_string(),
633 scope: "Token".to_string(),
634 url: "https://audit.com".to_string(),
635 date: None,
636 }],
637 metadata: vec![],
638 });
639 print_contract_report(&a, &quiet()).unwrap();
640 }
641
642 #[test]
643 fn test_print_report_external_sourcify_false() {
644 let mut a = minimal_analysis();
645 a.external_info = Some(scope::contract::external::ExternalInfo {
646 explorer_url: "https://etherscan.io/address/0xtest".to_string(),
647 github_repo: None,
648 sourcify_verified: Some(false),
649 deployer: None,
650 audit_reports: vec![],
651 metadata: vec![],
652 });
653 print_contract_report(&a, &quiet()).unwrap();
654 }
655
656 #[test]
657 fn test_print_report_access_control_empty_roles() {
658 let mut a = minimal_analysis();
659 a.access_control = Some(scope::contract::access::AccessControlMap {
660 ownership_pattern: Some("Ownable".to_string()),
661 has_renounced_ownership: false,
662 has_role_based_access: false,
663 uses_tx_origin: false,
664 tx_origin_locations: vec![],
665 modifiers: vec![],
666 privileged_functions: vec![],
667 roles: vec![],
668 auth_analysis: scope::contract::access::AuthAnalysis {
669 msg_sender_checks: 0,
670 tx_origin_checks: 0,
671 has_origin_sender_comparison: false,
672 summary: "No auth checks".to_string(),
673 },
674 });
675 print_contract_report(&a, &quiet()).unwrap();
676 }
677
678 #[test]
679 fn test_print_report_external_audit_with_url() {
680 let mut a = minimal_analysis();
681 a.external_info = Some(scope::contract::external::ExternalInfo {
682 explorer_url: "https://etherscan.io/address/0xtest".to_string(),
683 github_repo: None,
684 sourcify_verified: None,
685 deployer: None,
686 audit_reports: vec![scope::contract::external::AuditReport {
687 auditor: "CertiK".to_string(),
688 scope: "Full".to_string(),
689 url: "https://certik.com/audit.pdf".to_string(),
690 date: None,
691 }],
692 metadata: vec![],
693 });
694 print_contract_report(&a, &quiet()).unwrap();
695 }
696
697 #[test]
698 fn test_print_report_access_control_with_roles() {
699 let mut a = minimal_analysis();
700 a.access_control = Some(scope::contract::access::AccessControlMap {
701 ownership_pattern: None,
702 has_renounced_ownership: false,
703 has_role_based_access: true,
704 uses_tx_origin: false,
705 tx_origin_locations: vec![],
706 modifiers: vec![],
707 privileged_functions: vec![],
708 roles: vec!["ADMIN_ROLE".to_string(), "MINTER_ROLE".to_string()],
709 auth_analysis: scope::contract::access::AuthAnalysis {
710 msg_sender_checks: 2,
711 tx_origin_checks: 0,
712 has_origin_sender_comparison: false,
713 summary: "Role-based".to_string(),
714 },
715 });
716 print_contract_report(&a, &quiet()).unwrap();
717 }
718
719 #[test]
720 fn test_print_report_defi_empty_token_standards() {
721 let mut a = minimal_analysis();
722 a.defi_analysis = Some(scope::contract::defi::DefiAnalysis {
723 protocol_type: scope::contract::defi::ProtocolType::Other,
724 has_oracle_dependency: false,
725 oracle_info: vec![],
726 has_flash_loan_risk: false,
727 flash_loan_info: vec![],
728 dex_integrations: vec![],
729 lending_patterns: vec![],
730 token_standards: vec![],
731 staking_patterns: vec![],
732 risk_factors: vec![],
733 });
734 print_contract_report(&a, &quiet()).unwrap();
735 }
736
737 #[test]
738 fn test_print_report_proxy_no_impl_or_admin() {
739 let mut a = minimal_analysis();
740 a.proxy_info = Some(scope::contract::proxy::ProxyInfo {
741 is_proxy: true,
742 proxy_type: "Minimal Proxy".to_string(),
743 implementation_address: None,
744 admin_address: None,
745 beacon_address: None,
746 details: vec!["Minimal proxy".to_string()],
747 });
748 print_contract_report(&a, &quiet()).unwrap();
749 }
750
751 #[test]
752 fn test_print_report_defi_slippage_protected_no_deadline() {
753 let mut a = minimal_analysis();
754 a.defi_analysis = Some(scope::contract::defi::DefiAnalysis {
755 protocol_type: scope::contract::defi::ProtocolType::DEX,
756 has_oracle_dependency: false,
757 oracle_info: vec![],
758 has_flash_loan_risk: false,
759 flash_loan_info: vec![],
760 dex_integrations: vec![scope::contract::defi::DexIntegration {
761 dex: "SushiSwap".to_string(),
762 integration_type: "Swap".to_string(),
763 has_slippage_protection: true,
764 has_deadline_protection: false,
765 }],
766 lending_patterns: vec![],
767 token_standards: vec![],
768 staking_patterns: vec![],
769 risk_factors: vec![],
770 });
771 print_contract_report(&a, &quiet()).unwrap();
772 }
773}