1use serde::{Deserialize, Serialize};
2use solana_sdk::pubkey::Pubkey;
3use spl_token_2022_interface::extension::ExtensionType;
4use std::{fs, path::Path, str::FromStr};
5use toml;
6use utoipa::ToSchema;
7
8use crate::{
9 bundle::JitoConfig,
10 constant::{
11 DEFAULT_CACHE_ACCOUNT_TTL, DEFAULT_CACHE_DEFAULT_TTL,
12 DEFAULT_FEE_PAYER_BALANCE_METRICS_EXPIRY_SECONDS, DEFAULT_MAX_REQUEST_BODY_SIZE,
13 DEFAULT_MAX_TIMESTAMP_AGE, DEFAULT_METRICS_ENDPOINT, DEFAULT_METRICS_PORT,
14 DEFAULT_METRICS_SCRAPE_INTERVAL,
15 },
16 error::KoraError,
17 fee::price::{PriceConfig, PriceModel},
18 oracle::PriceSource,
19 sanitize_error,
20};
21
22pub use crate::usage_limit::{UsageLimitConfig, UsageLimitRuleConfig};
24
25#[derive(Clone, Deserialize)]
26pub struct Config {
27 pub validation: ValidationConfig,
28 pub kora: KoraConfig,
29 #[serde(default)]
30 pub metrics: MetricsConfig,
31}
32
33#[derive(Clone, Serialize, Deserialize, ToSchema)]
34pub struct MetricsConfig {
35 pub enabled: bool,
36 pub endpoint: String,
37 pub port: u16,
38 pub scrape_interval: u64,
39 #[serde(default)]
40 pub fee_payer_balance: FeePayerBalanceMetricsConfig,
41}
42
43impl Default for MetricsConfig {
44 fn default() -> Self {
45 Self {
46 enabled: false,
47 endpoint: DEFAULT_METRICS_ENDPOINT.to_string(),
48 port: DEFAULT_METRICS_PORT,
49 scrape_interval: DEFAULT_METRICS_SCRAPE_INTERVAL,
50 fee_payer_balance: FeePayerBalanceMetricsConfig::default(),
51 }
52 }
53}
54
55#[derive(Clone, Serialize, Deserialize, ToSchema)]
56pub struct FeePayerBalanceMetricsConfig {
57 pub enabled: bool,
58 pub expiry_seconds: u64,
59}
60
61impl Default for FeePayerBalanceMetricsConfig {
62 fn default() -> Self {
63 Self { enabled: false, expiry_seconds: DEFAULT_FEE_PAYER_BALANCE_METRICS_EXPIRY_SECONDS }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub enum SplTokenConfig {
69 All,
70 #[serde(untagged)]
71 Allowlist(Vec<String>),
72}
73
74impl Default for SplTokenConfig {
75 fn default() -> Self {
76 SplTokenConfig::Allowlist(vec![])
77 }
78}
79
80impl<'a> IntoIterator for &'a SplTokenConfig {
81 type Item = &'a String;
82 type IntoIter = std::slice::Iter<'a, String>;
83
84 fn into_iter(self) -> Self::IntoIter {
85 match self {
86 SplTokenConfig::All => [].iter(),
87 SplTokenConfig::Allowlist(tokens) => tokens.iter(),
88 }
89 }
90}
91
92impl SplTokenConfig {
93 pub fn has_token(&self, token: &str) -> bool {
94 match self {
95 SplTokenConfig::All => true,
96 SplTokenConfig::Allowlist(tokens) => tokens.iter().any(|s| s == token),
97 }
98 }
99
100 pub fn has_tokens(&self) -> bool {
101 match self {
102 SplTokenConfig::All => true,
103 SplTokenConfig::Allowlist(tokens) => !tokens.is_empty(),
104 }
105 }
106
107 pub fn as_slice(&self) -> &[String] {
108 match self {
109 SplTokenConfig::All => &[],
110 SplTokenConfig::Allowlist(v) => v.as_slice(),
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
116pub struct ValidationConfig {
117 pub max_allowed_lamports: u64,
118 pub max_signatures: u64,
119 pub allowed_programs: Vec<String>,
120 pub allowed_tokens: Vec<String>,
121 pub allowed_spl_paid_tokens: SplTokenConfig,
122 pub disallowed_accounts: Vec<String>,
123 pub price_source: PriceSource,
124 #[serde(default)] pub fee_payer_policy: FeePayerPolicy,
126 #[serde(default)]
127 pub price: PriceConfig,
128 #[serde(default)]
129 pub token_2022: Token2022Config,
130 #[serde(default)]
134 pub allow_durable_transactions: bool,
135}
136
137impl ValidationConfig {
138 pub fn is_payment_required(&self) -> bool {
139 !matches!(&self.price.model, PriceModel::Free)
140 }
141
142 pub fn supports_token(&self, token: &str) -> bool {
143 self.allowed_spl_paid_tokens.has_token(token)
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Default)]
148pub struct FeePayerPolicy {
149 #[serde(default)]
150 pub system: SystemInstructionPolicy,
151 #[serde(default)]
152 pub spl_token: SplTokenInstructionPolicy,
153 #[serde(default)]
154 pub token_2022: Token2022InstructionPolicy,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Default)]
158pub struct SystemInstructionPolicy {
159 pub allow_transfer: bool,
161 pub allow_assign: bool,
163 pub allow_create_account: bool,
165 pub allow_allocate: bool,
167 #[serde(default)]
169 pub nonce: NonceInstructionPolicy,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Default)]
173pub struct NonceInstructionPolicy {
174 pub allow_initialize: bool,
176 pub allow_advance: bool,
178 pub allow_withdraw: bool,
180 pub allow_authorize: bool,
182 }
184
185#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Default)]
186pub struct SplTokenInstructionPolicy {
187 pub allow_transfer: bool,
189 pub allow_burn: bool,
191 pub allow_close_account: bool,
193 pub allow_approve: bool,
195 pub allow_revoke: bool,
197 pub allow_set_authority: bool,
199 pub allow_mint_to: bool,
201 pub allow_initialize_mint: bool,
203 pub allow_initialize_account: bool,
205 pub allow_initialize_multisig: bool,
207 pub allow_freeze_account: bool,
209 pub allow_thaw_account: bool,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Default)]
214pub struct Token2022InstructionPolicy {
215 pub allow_transfer: bool,
217 pub allow_burn: bool,
219 pub allow_close_account: bool,
221 pub allow_approve: bool,
223 pub allow_revoke: bool,
225 pub allow_set_authority: bool,
227 pub allow_mint_to: bool,
229 pub allow_initialize_mint: bool,
231 pub allow_initialize_account: bool,
233 pub allow_initialize_multisig: bool,
235 pub allow_freeze_account: bool,
237 pub allow_thaw_account: bool,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
242pub struct Token2022Config {
243 pub blocked_mint_extensions: Vec<String>,
244 pub blocked_account_extensions: Vec<String>,
245 #[serde(skip)]
246 parsed_blocked_mint_extensions: Option<Vec<ExtensionType>>,
247 #[serde(skip)]
248 parsed_blocked_account_extensions: Option<Vec<ExtensionType>>,
249}
250
251impl Default for Token2022Config {
252 fn default() -> Self {
253 Self {
254 blocked_mint_extensions: Vec::new(),
255 blocked_account_extensions: Vec::new(),
256 parsed_blocked_mint_extensions: Some(Vec::new()),
257 parsed_blocked_account_extensions: Some(Vec::new()),
258 }
259 }
260}
261
262impl Token2022Config {
263 pub fn initialize(&mut self) -> Result<(), String> {
266 let mut mint_extensions = Vec::new();
267 for name in &self.blocked_mint_extensions {
268 match crate::token::spl_token_2022_util::parse_mint_extension_string(name) {
269 Some(ext) => {
270 mint_extensions.push(ext);
271 }
272 None => {
273 return Err(format!(
274 "Invalid mint extension name: '{}'. Valid names are: {:?}",
275 name,
276 crate::token::spl_token_2022_util::get_all_mint_extension_names()
277 ));
278 }
279 }
280 }
281 self.parsed_blocked_mint_extensions = Some(mint_extensions);
282
283 let mut account_extensions = Vec::new();
284 for name in &self.blocked_account_extensions {
285 match crate::token::spl_token_2022_util::parse_account_extension_string(name) {
286 Some(ext) => {
287 account_extensions.push(ext);
288 }
289 None => {
290 return Err(format!(
291 "Invalid account extension name: '{}'. Valid names are: {:?}",
292 name,
293 crate::token::spl_token_2022_util::get_all_account_extension_names()
294 ));
295 }
296 }
297 }
298 self.parsed_blocked_account_extensions = Some(account_extensions);
299
300 Ok(())
301 }
302
303 pub fn get_blocked_mint_extensions(&self) -> &[ExtensionType] {
305 self.parsed_blocked_mint_extensions.as_deref().unwrap_or(&[])
306 }
307
308 pub fn get_blocked_account_extensions(&self) -> &[ExtensionType] {
310 self.parsed_blocked_account_extensions.as_deref().unwrap_or(&[])
311 }
312
313 pub fn is_mint_extension_blocked(&self, ext: ExtensionType) -> bool {
315 self.get_blocked_mint_extensions().contains(&ext)
316 }
317
318 pub fn is_account_extension_blocked(&self, ext: ExtensionType) -> bool {
320 self.get_blocked_account_extensions().contains(&ext)
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
325pub struct EnabledMethods {
326 pub liveness: bool,
327 pub estimate_transaction_fee: bool,
328 pub get_supported_tokens: bool,
329 pub get_payer_signer: bool,
330 pub sign_transaction: bool,
331 pub sign_and_send_transaction: bool,
332 pub transfer_transaction: bool,
333 pub get_blockhash: bool,
334 pub get_config: bool,
335 pub get_version: bool,
336 #[serde(default)]
338 pub estimate_bundle_fee: bool,
339 #[serde(default)]
340 pub sign_and_send_bundle: bool,
341 #[serde(default)]
342 pub sign_bundle: bool,
343}
344
345impl EnabledMethods {
346 pub fn iter(&self) -> impl Iterator<Item = bool> {
347 [
348 self.liveness,
349 self.estimate_transaction_fee,
350 self.get_supported_tokens,
351 self.get_payer_signer,
352 self.sign_transaction,
353 self.sign_and_send_transaction,
354 self.transfer_transaction,
355 self.get_blockhash,
356 self.get_config,
357 self.get_version,
358 self.estimate_bundle_fee,
359 self.sign_and_send_bundle,
360 self.sign_bundle,
361 ]
362 .into_iter()
363 }
364
365 pub fn get_enabled_method_names(&self) -> Vec<String> {
367 let mut methods = Vec::new();
368 if self.liveness {
369 methods.push("liveness".to_string());
370 }
371 if self.estimate_transaction_fee {
372 methods.push("estimateTransactionFee".to_string());
373 }
374 if self.estimate_bundle_fee {
375 methods.push("estimateBundleFee".to_string());
376 }
377 if self.get_supported_tokens {
378 methods.push("getSupportedTokens".to_string());
379 }
380 if self.get_payer_signer {
381 methods.push("getPayerSigner".to_string());
382 }
383 if self.sign_transaction {
384 methods.push("signTransaction".to_string());
385 }
386 if self.sign_and_send_transaction {
387 methods.push("signAndSendTransaction".to_string());
388 }
389 if self.transfer_transaction {
390 methods.push("transferTransaction".to_string());
391 }
392 if self.get_blockhash {
393 methods.push("getBlockhash".to_string());
394 }
395 if self.get_config {
396 methods.push("getConfig".to_string());
397 }
398 if self.get_version {
399 methods.push("getVersion".to_string());
400 }
401 if self.sign_and_send_bundle {
402 methods.push("signAndSendBundle".to_string());
403 }
404 if self.sign_bundle {
405 methods.push("signBundle".to_string());
406 }
407 methods
408 }
409}
410
411impl IntoIterator for &EnabledMethods {
412 type Item = bool;
413 type IntoIter = std::array::IntoIter<bool, 13>;
414
415 fn into_iter(self) -> Self::IntoIter {
416 [
417 self.liveness,
418 self.estimate_transaction_fee,
419 self.get_supported_tokens,
420 self.get_payer_signer,
421 self.sign_transaction,
422 self.sign_and_send_transaction,
423 self.transfer_transaction,
424 self.get_blockhash,
425 self.get_config,
426 self.get_version,
427 self.estimate_bundle_fee,
428 self.sign_and_send_bundle,
429 self.sign_bundle,
430 ]
431 .into_iter()
432 }
433}
434
435impl Default for EnabledMethods {
436 fn default() -> Self {
437 Self {
438 liveness: true,
439 estimate_transaction_fee: true,
440 get_supported_tokens: true,
441 get_payer_signer: true,
442 sign_transaction: true,
443 sign_and_send_transaction: true,
444 transfer_transaction: true,
445 get_blockhash: true,
446 get_config: true,
447 get_version: true,
448 estimate_bundle_fee: false,
450 sign_and_send_bundle: false,
451 sign_bundle: false,
452 }
453 }
454}
455
456fn default_max_timestamp_age() -> i64 {
457 DEFAULT_MAX_TIMESTAMP_AGE
458}
459
460fn default_max_request_body_size() -> usize {
461 DEFAULT_MAX_REQUEST_BODY_SIZE
462}
463
464#[derive(Clone, Serialize, Deserialize, ToSchema)]
465pub struct CacheConfig {
466 pub url: Option<String>,
468 pub enabled: bool,
470 pub default_ttl: u64,
472 pub account_ttl: u64,
474}
475
476impl Default for CacheConfig {
477 fn default() -> Self {
478 Self {
479 url: None,
480 enabled: false,
481 default_ttl: DEFAULT_CACHE_DEFAULT_TTL,
482 account_ttl: DEFAULT_CACHE_ACCOUNT_TTL,
483 }
484 }
485}
486
487#[derive(Clone, Serialize, Deserialize, ToSchema)]
488pub struct KoraConfig {
489 pub rate_limit: u64,
490 #[serde(default = "default_max_request_body_size")]
491 pub max_request_body_size: usize,
492 #[serde(default)]
493 pub enabled_methods: EnabledMethods,
494 #[serde(default)]
495 pub auth: AuthConfig,
496 pub payment_address: Option<String>,
498 #[serde(default)]
499 pub cache: CacheConfig,
500 #[serde(default)]
501 pub usage_limit: UsageLimitConfig,
502 #[serde(default)]
504 pub bundle: BundleConfig,
505}
506
507impl Default for KoraConfig {
508 fn default() -> Self {
509 Self {
510 rate_limit: 100,
511 max_request_body_size: DEFAULT_MAX_REQUEST_BODY_SIZE,
512 enabled_methods: EnabledMethods::default(),
513 auth: AuthConfig::default(),
514 payment_address: None,
515 cache: CacheConfig::default(),
516 usage_limit: UsageLimitConfig::default(),
517 bundle: BundleConfig::default(),
518 }
519 }
520}
521
522#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
524pub struct BundleConfig {
525 #[serde(default)]
527 pub enabled: bool,
528 #[serde(default)]
530 pub jito: JitoConfig,
531}
532
533#[derive(Clone, Serialize, Deserialize, ToSchema)]
534pub struct AuthConfig {
535 pub api_key: Option<String>,
536 pub hmac_secret: Option<String>,
537 #[serde(default = "default_max_timestamp_age")]
538 pub max_timestamp_age: i64,
539}
540
541impl Default for AuthConfig {
542 fn default() -> Self {
543 Self { api_key: None, hmac_secret: None, max_timestamp_age: DEFAULT_MAX_TIMESTAMP_AGE }
544 }
545}
546
547impl Config {
548 pub fn load_config<P: AsRef<Path>>(path: P) -> Result<Config, KoraError> {
549 let contents = fs::read_to_string(path).map_err(|e| {
550 KoraError::InternalServerError(format!(
551 "Failed to read config file: {}",
552 sanitize_error!(e)
553 ))
554 })?;
555
556 let mut config: Config = toml::from_str(&contents).map_err(|e| {
557 KoraError::InternalServerError(format!(
558 "Failed to parse config file: {}",
559 sanitize_error!(e)
560 ))
561 })?;
562
563 config.validation.token_2022.initialize().map_err(|e| {
565 KoraError::InternalServerError(format!(
566 "Failed to initialize Token2022 config: {}",
567 sanitize_error!(e)
568 ))
569 })?;
570
571 Ok(config)
572 }
573}
574
575impl KoraConfig {
576 pub fn get_payment_address(&self, signer_pubkey: &Pubkey) -> Result<Pubkey, KoraError> {
578 if let Some(payment_address_str) = &self.payment_address {
579 let payment_address = Pubkey::from_str(payment_address_str).map_err(|_| {
580 KoraError::InternalServerError("Invalid payment_address format".to_string())
581 })?;
582 Ok(payment_address)
583 } else {
584 Ok(*signer_pubkey)
585 }
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use crate::{
592 fee::price::PriceModel,
593 tests::toml_mock::{create_invalid_config, ConfigBuilder},
594 };
595
596 use super::*;
597
598 #[test]
599 fn test_load_valid_config() {
600 let config = ConfigBuilder::new()
601 .with_programs(vec!["program1", "program2"])
602 .with_tokens(vec!["token1", "token2"])
603 .with_spl_paid_tokens(SplTokenConfig::Allowlist(vec!["token3".to_string()]))
604 .with_disallowed_accounts(vec!["account1"])
605 .build_config()
606 .unwrap();
607
608 assert_eq!(config.validation.max_allowed_lamports, 1000000000);
609 assert_eq!(config.validation.max_signatures, 10);
610 assert_eq!(config.validation.allowed_programs, vec!["program1", "program2"]);
611 assert_eq!(config.validation.allowed_tokens, vec!["token1", "token2"]);
612 assert_eq!(
613 config.validation.allowed_spl_paid_tokens,
614 SplTokenConfig::Allowlist(vec!["token3".to_string()])
615 );
616 assert_eq!(config.validation.disallowed_accounts, vec!["account1"]);
617 assert_eq!(config.validation.price_source, PriceSource::Jupiter);
618 assert_eq!(config.kora.rate_limit, 100);
619 assert!(config.kora.enabled_methods.estimate_transaction_fee);
620 assert!(config.kora.enabled_methods.sign_and_send_transaction);
621 }
622
623 #[test]
624 fn test_load_config_with_enabled_methods() {
625 let config = ConfigBuilder::new()
626 .with_programs(vec!["program1", "program2"])
627 .with_tokens(vec!["token1", "token2"])
628 .with_spl_paid_tokens(SplTokenConfig::Allowlist(vec!["token3".to_string()]))
629 .with_disallowed_accounts(vec!["account1"])
630 .with_enabled_methods(&[
631 ("liveness", true),
632 ("estimate_transaction_fee", false),
633 ("get_supported_tokens", true),
634 ("sign_transaction", true),
635 ("sign_and_send_transaction", false),
636 ("transfer_transaction", true),
637 ("get_blockhash", true),
638 ("get_config", true),
639 ("get_payer_signer", true),
640 ("get_version", true),
641 ])
642 .build_config()
643 .unwrap();
644
645 assert_eq!(config.kora.rate_limit, 100);
646 assert!(config.kora.enabled_methods.liveness);
647 assert!(!config.kora.enabled_methods.estimate_transaction_fee);
648 assert!(config.kora.enabled_methods.get_supported_tokens);
649 assert!(config.kora.enabled_methods.sign_transaction);
650 assert!(!config.kora.enabled_methods.sign_and_send_transaction);
651 assert!(config.kora.enabled_methods.transfer_transaction);
652 assert!(config.kora.enabled_methods.get_blockhash);
653 assert!(config.kora.enabled_methods.get_config);
654 }
655
656 #[test]
657 fn test_load_invalid_config() {
658 let result = create_invalid_config("invalid toml content");
659 assert!(result.is_err());
660 }
661
662 #[test]
663 fn test_load_nonexistent_file() {
664 let result = Config::load_config("nonexistent_file.toml");
665 assert!(result.is_err());
666 }
667
668 #[test]
669 fn test_parse_spl_payment_config() {
670 let config =
671 ConfigBuilder::new().with_spl_paid_tokens(SplTokenConfig::All).build_config().unwrap();
672
673 assert_eq!(config.validation.allowed_spl_paid_tokens, SplTokenConfig::All);
674 }
675
676 #[test]
677 fn test_parse_margin_price_config() {
678 let config = ConfigBuilder::new().with_margin_price(0.1).build_config().unwrap();
679
680 match &config.validation.price.model {
681 PriceModel::Margin { margin } => {
682 assert_eq!(*margin, 0.1);
683 }
684 _ => panic!("Expected Margin price model"),
685 }
686 }
687
688 #[test]
689 fn test_parse_fixed_price_config() {
690 let config = ConfigBuilder::new()
691 .with_fixed_price(1000000, "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")
692 .build_config()
693 .unwrap();
694
695 match &config.validation.price.model {
696 PriceModel::Fixed { amount, token, strict } => {
697 assert_eq!(*amount, 1000000);
698 assert_eq!(token, "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
699 assert!(!strict);
700 }
701 _ => panic!("Expected Fixed price model"),
702 }
703 }
704
705 #[test]
706 fn test_parse_free_price_config() {
707 let config = ConfigBuilder::new().with_free_price().build_config().unwrap();
708
709 match &config.validation.price.model {
710 PriceModel::Free => {
711 }
713 _ => panic!("Expected Free price model"),
714 }
715 }
716
717 #[test]
718 fn test_parse_missing_price_config() {
719 let config = ConfigBuilder::new().build_config().unwrap();
720
721 match &config.validation.price.model {
723 PriceModel::Margin { margin } => {
724 assert_eq!(*margin, 0.0);
725 }
726 _ => panic!("Expected default Margin price model with 0.0 margin"),
727 }
728 }
729
730 #[test]
731 fn test_parse_invalid_price_config() {
732 let result = ConfigBuilder::new().with_invalid_price("invalid_type").build_config();
733
734 assert!(result.is_err());
735 if let Err(KoraError::InternalServerError(msg)) = result {
736 assert!(msg.contains("Failed to parse config file"));
737 } else {
738 panic!("Expected InternalServerError with parsing failure message");
739 }
740 }
741
742 #[test]
743 fn test_token2022_config_parsing() {
744 let config = ConfigBuilder::new()
745 .with_token2022_extensions(
746 vec!["transfer_fee_config", "pausable"],
747 vec!["memo_transfer", "cpi_guard"],
748 )
749 .build_config()
750 .unwrap();
751
752 assert_eq!(
753 config.validation.token_2022.blocked_mint_extensions,
754 vec!["transfer_fee_config", "pausable"]
755 );
756 assert_eq!(
757 config.validation.token_2022.blocked_account_extensions,
758 vec!["memo_transfer", "cpi_guard"]
759 );
760
761 let mint_extensions = config.validation.token_2022.get_blocked_mint_extensions();
762 assert_eq!(mint_extensions.len(), 2);
763
764 let account_extensions = config.validation.token_2022.get_blocked_account_extensions();
765 assert_eq!(account_extensions.len(), 2);
766 }
767
768 #[test]
769 fn test_token2022_config_invalid_extension() {
770 let result = ConfigBuilder::new()
771 .with_token2022_extensions(vec!["invalid_extension"], vec![])
772 .build_config();
773
774 assert!(result.is_err());
775 if let Err(KoraError::InternalServerError(msg)) = result {
776 assert!(msg.contains("Failed to initialize Token2022 config"));
777 assert!(msg.contains("Invalid mint extension name: 'invalid_extension'"));
778 } else {
779 panic!("Expected InternalServerError with Token2022 initialization failure");
780 }
781 }
782
783 #[test]
784 fn test_token2022_config_default() {
785 let config = ConfigBuilder::new().build_config().unwrap();
786
787 assert!(config.validation.token_2022.blocked_mint_extensions.is_empty());
788 assert!(config.validation.token_2022.blocked_account_extensions.is_empty());
789
790 assert!(config.validation.token_2022.get_blocked_mint_extensions().is_empty());
791 assert!(config.validation.token_2022.get_blocked_account_extensions().is_empty());
792 }
793
794 #[test]
795 fn test_token2022_extension_blocking_check() {
796 let config = ConfigBuilder::new()
797 .with_token2022_extensions(
798 vec!["transfer_fee_config", "pausable"],
799 vec!["memo_transfer"],
800 )
801 .build_config()
802 .unwrap();
803
804 assert!(config
806 .validation
807 .token_2022
808 .is_mint_extension_blocked(ExtensionType::TransferFeeConfig));
809 assert!(config.validation.token_2022.is_mint_extension_blocked(ExtensionType::Pausable));
810 assert!(!config
811 .validation
812 .token_2022
813 .is_mint_extension_blocked(ExtensionType::NonTransferable));
814
815 assert!(config
817 .validation
818 .token_2022
819 .is_account_extension_blocked(ExtensionType::MemoTransfer));
820 assert!(!config
821 .validation
822 .token_2022
823 .is_account_extension_blocked(ExtensionType::CpiGuard));
824 }
825
826 #[test]
827 fn test_cache_config_parsing() {
828 let config = ConfigBuilder::new()
829 .with_cache_config(Some("redis://localhost:6379"), true, 600, 120)
830 .build_config()
831 .unwrap();
832
833 assert_eq!(config.kora.cache.url, Some("redis://localhost:6379".to_string()));
834 assert!(config.kora.cache.enabled);
835 assert_eq!(config.kora.cache.default_ttl, 600);
836 assert_eq!(config.kora.cache.account_ttl, 120);
837 }
838
839 #[test]
840 fn test_cache_config_default() {
841 let config = ConfigBuilder::new().build_config().unwrap();
842
843 assert_eq!(config.kora.cache.url, None);
844 assert!(!config.kora.cache.enabled);
845 assert_eq!(config.kora.cache.default_ttl, 300);
846 assert_eq!(config.kora.cache.account_ttl, 60);
847 }
848
849 #[test]
850 fn test_max_request_body_size_default() {
851 let config = ConfigBuilder::new().build_config().unwrap();
852
853 assert_eq!(config.kora.max_request_body_size, DEFAULT_MAX_REQUEST_BODY_SIZE);
854 assert_eq!(config.kora.max_request_body_size, 2 * 1024 * 1024); }
856
857 #[test]
858 fn test_max_request_body_size_custom() {
859 let custom_size = 10 * 1024 * 1024; let config =
861 ConfigBuilder::new().with_max_request_body_size(custom_size).build_config().unwrap();
862
863 assert_eq!(config.kora.max_request_body_size, custom_size);
864 }
865}