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