1use jsonrpc_core::{BoxFuture, Error as JsonRpcCoreError, ErrorCode, Result};
2use jsonrpc_derive::rpc;
3use solana_client::{
4 rpc_config::{
5 RpcAccountInfoConfig, RpcLargestAccountsConfig, RpcProgramAccountsConfig, RpcSupplyConfig,
6 RpcTokenAccountsFilter,
7 },
8 rpc_request::TokenAccountsFilter,
9 rpc_response::{
10 OptionalContext, RpcAccountBalance, RpcKeyedAccount, RpcResponseContext, RpcSupply,
11 RpcTokenAccountBalance,
12 },
13};
14use solana_commitment_config::CommitmentConfig;
15use solana_rpc_client_api::response::Response as RpcResponse;
16
17use super::{utils::verify_pubkey, RunloopContext, State, SurfnetRpcContext};
18use crate::surfnet::locker::SvmAccessContext;
19
20#[rpc]
21pub trait AccountsScan {
22 type Metadata;
23
24 #[rpc(meta, name = "getProgramAccounts")]
105 fn get_program_accounts(
106 &self,
107 meta: Self::Metadata,
108 program_id_str: String,
109 config: Option<RpcProgramAccountsConfig>,
110 ) -> BoxFuture<Result<OptionalContext<Vec<RpcKeyedAccount>>>>;
111
112 #[rpc(meta, name = "getLargestAccounts")]
167 fn get_largest_accounts(
168 &self,
169 meta: Self::Metadata,
170 config: Option<RpcLargestAccountsConfig>,
171 ) -> BoxFuture<Result<RpcResponse<Vec<RpcAccountBalance>>>>;
172
173 #[rpc(meta, name = "getSupply")]
229 fn get_supply(
230 &self,
231 meta: Self::Metadata,
232 config: Option<RpcSupplyConfig>,
233 ) -> BoxFuture<Result<RpcResponse<RpcSupply>>>;
234
235 #[rpc(meta, name = "getTokenLargestAccounts")]
298 fn get_token_largest_accounts(
299 &self,
300 meta: Self::Metadata,
301 mint_str: String,
302 commitment: Option<CommitmentConfig>,
303 ) -> BoxFuture<Result<RpcResponse<Vec<RpcTokenAccountBalance>>>>;
304
305 #[rpc(meta, name = "getTokenAccountsByOwner")]
376 fn get_token_accounts_by_owner(
377 &self,
378 meta: Self::Metadata,
379 owner_str: String,
380 token_account_filter: RpcTokenAccountsFilter,
381 config: Option<RpcAccountInfoConfig>,
382 ) -> BoxFuture<Result<RpcResponse<Vec<RpcKeyedAccount>>>>;
383
384 #[rpc(meta, name = "getTokenAccountsByDelegate")]
447 fn get_token_accounts_by_delegate(
448 &self,
449 meta: Self::Metadata,
450 delegate_str: String,
451 token_account_filter: RpcTokenAccountsFilter,
452 config: Option<RpcAccountInfoConfig>,
453 ) -> BoxFuture<Result<RpcResponse<Vec<RpcKeyedAccount>>>>;
454}
455
456#[derive(Clone)]
457pub struct SurfpoolAccountsScanRpc;
458impl AccountsScan for SurfpoolAccountsScanRpc {
459 type Metadata = Option<RunloopContext>;
460
461 fn get_program_accounts(
462 &self,
463 meta: Self::Metadata,
464 program_id_str: String,
465 config: Option<RpcProgramAccountsConfig>,
466 ) -> BoxFuture<Result<OptionalContext<Vec<RpcKeyedAccount>>>> {
467 let config = config.unwrap_or_default();
468 let program_id = match verify_pubkey(&program_id_str) {
469 Ok(res) => res,
470 Err(e) => return e.into(),
471 };
472
473 let SurfnetRpcContext {
474 svm_locker,
475 remote_ctx,
476 } = match meta.get_rpc_context(()) {
477 Ok(res) => res,
478 Err(e) => return e.into(),
479 };
480
481 Box::pin(async move {
482 let current_slot = svm_locker.get_latest_absolute_slot();
483
484 let account_config = config.account_config;
485
486 if let Some(min_context_slot_val) = account_config.min_context_slot.as_ref() {
487 if current_slot < *min_context_slot_val {
488 return Err(JsonRpcCoreError {
489 code: ErrorCode::InternalError,
490 message: format!(
491 "Node's current slot {} is less than requested minContextSlot {}",
492 current_slot, min_context_slot_val
493 ),
494 data: None,
495 });
496 }
497 }
498
499 let program_accounts = svm_locker
501 .get_program_accounts(
502 &remote_ctx.map(|(client, _)| client),
503 &program_id,
504 account_config,
505 config.filters,
506 )
507 .await?
508 .inner;
509
510 if config.with_context.unwrap_or(false) {
511 Ok(OptionalContext::Context(RpcResponse {
512 context: RpcResponseContext::new(current_slot),
513 value: program_accounts,
514 }))
515 } else {
516 Ok(OptionalContext::NoContext(program_accounts))
517 }
518 })
519 }
520
521 fn get_largest_accounts(
522 &self,
523 meta: Self::Metadata,
524 config: Option<RpcLargestAccountsConfig>,
525 ) -> BoxFuture<Result<RpcResponse<Vec<RpcAccountBalance>>>> {
526 let config = config.unwrap_or_default();
527 let SurfnetRpcContext {
528 svm_locker,
529 remote_ctx,
530 } = match meta.get_rpc_context(config.commitment.unwrap_or_default()) {
531 Ok(res) => res,
532 Err(e) => return e.into(),
533 };
534
535 Box::pin(async move {
536 let current_slot = svm_locker.get_latest_absolute_slot();
537
538 let largest_accounts = svm_locker
539 .get_largest_accounts(&remote_ctx, config)
540 .await?
541 .inner;
542
543 Ok(RpcResponse {
544 context: RpcResponseContext::new(current_slot),
545 value: largest_accounts,
546 })
547 })
548 }
549
550 fn get_supply(
551 &self,
552 meta: Self::Metadata,
553 config: Option<RpcSupplyConfig>,
554 ) -> BoxFuture<Result<RpcResponse<RpcSupply>>> {
555 let svm_locker = match meta.get_svm_locker() {
556 Ok(locker) => locker,
557 Err(e) => return e.into(),
558 };
559
560 Box::pin(async move {
561 svm_locker.with_svm_reader(|svm_reader| {
562 let slot = svm_reader.get_latest_absolute_slot();
563
564 let exclude_accounts = config
566 .as_ref()
567 .map(|c| c.exclude_non_circulating_accounts_list)
568 .unwrap_or(false);
569
570 Ok(RpcResponse {
571 context: RpcResponseContext::new(slot),
572 value: RpcSupply {
573 total: svm_reader.total_supply,
574 circulating: svm_reader.circulating_supply,
575 non_circulating: svm_reader.non_circulating_supply,
576 non_circulating_accounts: if exclude_accounts {
577 vec![]
578 } else {
579 svm_reader.non_circulating_accounts.clone()
580 },
581 },
582 })
583 })
584 })
585 }
586
587 fn get_token_largest_accounts(
588 &self,
589 meta: Self::Metadata,
590 mint_str: String,
591 commitment: Option<CommitmentConfig>,
592 ) -> BoxFuture<Result<RpcResponse<Vec<RpcTokenAccountBalance>>>> {
593 let mint = match verify_pubkey(&mint_str) {
594 Ok(res) => res,
595 Err(e) => return e.into(),
596 };
597
598 let SurfnetRpcContext {
599 svm_locker,
600 remote_ctx,
601 } = match meta.get_rpc_context(commitment.unwrap_or_default()) {
602 Ok(res) => res,
603 Err(e) => return e.into(),
604 };
605
606 Box::pin(async move {
607 let SvmAccessContext {
608 slot,
609 inner: largest_accounts,
610 ..
611 } = svm_locker
612 .get_token_largest_accounts(&remote_ctx, &mint)
613 .await?;
614
615 Ok(RpcResponse {
616 context: RpcResponseContext::new(slot),
617 value: largest_accounts,
618 })
619 })
620 }
621
622 fn get_token_accounts_by_owner(
623 &self,
624 meta: Self::Metadata,
625 owner_str: String,
626 token_account_filter: RpcTokenAccountsFilter,
627 config: Option<RpcAccountInfoConfig>,
628 ) -> BoxFuture<Result<RpcResponse<Vec<RpcKeyedAccount>>>> {
629 let config = config.unwrap_or_default();
630 let owner = match verify_pubkey(&owner_str) {
631 Ok(res) => res,
632 Err(e) => return e.into(),
633 };
634
635 let filter = match token_account_filter {
636 RpcTokenAccountsFilter::Mint(mint_str) => {
637 let mint = match verify_pubkey(&mint_str) {
638 Ok(res) => res,
639 Err(e) => return e.into(),
640 };
641 TokenAccountsFilter::Mint(mint)
642 }
643 RpcTokenAccountsFilter::ProgramId(program_id_str) => {
644 let program_id = match verify_pubkey(&program_id_str) {
645 Ok(res) => res,
646 Err(e) => return e.into(),
647 };
648 TokenAccountsFilter::ProgramId(program_id)
649 }
650 };
651
652 let SurfnetRpcContext {
653 svm_locker,
654 remote_ctx,
655 } = match meta.get_rpc_context(()) {
656 Ok(res) => res,
657 Err(e) => return e.into(),
658 };
659
660 Box::pin(async move {
661 let SvmAccessContext {
662 slot,
663 inner: token_accounts,
664 ..
665 } = svm_locker
666 .get_token_accounts_by_owner(
667 &remote_ctx.map(|(client, _)| client),
668 owner,
669 &filter,
670 &config,
671 )
672 .await?;
673
674 Ok(RpcResponse {
675 context: RpcResponseContext::new(slot),
676 value: token_accounts,
677 })
678 })
679 }
680
681 fn get_token_accounts_by_delegate(
682 &self,
683 meta: Self::Metadata,
684 delegate_str: String,
685 token_account_filter: RpcTokenAccountsFilter,
686 config: Option<RpcAccountInfoConfig>,
687 ) -> BoxFuture<Result<RpcResponse<Vec<RpcKeyedAccount>>>> {
688 let config = config.unwrap_or_default();
689 let delegate = match verify_pubkey(&delegate_str) {
690 Ok(res) => res,
691 Err(e) => return e.into(),
692 };
693
694 let SurfnetRpcContext {
695 svm_locker,
696 remote_ctx,
697 } = match meta.get_rpc_context(config.commitment.unwrap_or_default()) {
698 Ok(res) => res,
699 Err(e) => return e.into(),
700 };
701
702 Box::pin(async move {
703 let filter = match token_account_filter {
704 RpcTokenAccountsFilter::Mint(mint_str) => {
705 TokenAccountsFilter::Mint(verify_pubkey(&mint_str)?)
706 }
707 RpcTokenAccountsFilter::ProgramId(program_id_str) => {
708 TokenAccountsFilter::ProgramId(verify_pubkey(&program_id_str)?)
709 }
710 };
711
712 let remote_ctx = remote_ctx.map(|(r, _)| r);
713 let SvmAccessContext {
714 slot,
715 inner: keyed_accounts,
716 ..
717 } = svm_locker
718 .get_token_accounts_by_delegate(&remote_ctx, delegate, &filter, &config)
719 .await?;
720
721 Ok(RpcResponse {
722 context: RpcResponseContext::new(slot),
723 value: keyed_accounts,
724 })
725 })
726 }
727}
728
729#[cfg(test)]
730mod tests {
731
732 use core::panic;
733 use std::str::FromStr;
734
735 use solana_account::Account;
736 use solana_client::{
737 rpc_config::{
738 RpcLargestAccountsConfig, RpcLargestAccountsFilter, RpcProgramAccountsConfig,
739 RpcSupplyConfig, RpcTokenAccountsFilter,
740 },
741 rpc_filter::{Memcmp, RpcFilterType},
742 rpc_response::OptionalContext,
743 };
744 use solana_pubkey::Pubkey;
745 use solana_sdk::program_pack::Pack;
746 use spl_token::state::Account as TokenAccount;
747 use surfpool_types::SupplyUpdate;
748
749 use super::{AccountsScan, SurfpoolAccountsScanRpc};
750 use crate::{
751 rpc::surfnet_cheatcodes::{SurfnetCheatcodesRpc, SvmTricksRpc},
752 tests::helpers::TestSetup,
753 };
754
755 const VALID_PUBKEY_1: &str = "11111111111111111111111111111112";
756
757 #[tokio::test(flavor = "multi_thread")]
758 async fn test_get_program_accounts() {
759 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
760
761 let owner_pubkey = Pubkey::new_unique();
763 let owned_pubkey_short_data = Pubkey::new_unique();
765 let owner_pubkey_long_data = Pubkey::new_unique();
766 let other_pubkey = Pubkey::new_unique();
768
769 setup.context.svm_locker.with_svm_writer(|svm_writer| {
770 svm_writer
771 .set_account(
772 &owned_pubkey_short_data,
773 Account {
774 lamports: 1000,
775 data: vec![4, 5, 6],
776 owner: owner_pubkey,
777 executable: false,
778 rent_epoch: 0,
779 },
780 )
781 .unwrap();
782
783 svm_writer
784 .set_account(
785 &owner_pubkey_long_data,
786 Account {
787 lamports: 2000,
788 data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
789 owner: owner_pubkey,
790 executable: false,
791 rent_epoch: 0,
792 },
793 )
794 .unwrap();
795
796 svm_writer
797 .set_account(
798 &other_pubkey,
799 Account {
800 lamports: 500,
801 data: vec![4, 5, 6],
802 owner: Pubkey::new_unique(),
803 executable: false,
804 rent_epoch: 0,
805 },
806 )
807 .unwrap();
808 });
809
810 {
812 let res = setup
813 .rpc
814 .get_program_accounts(Some(setup.context.clone()), owner_pubkey.to_string(), None)
815 .await
816 .expect("Failed to get program accounts");
817 match res {
818 OptionalContext::Context(_) => {
819 panic!("Expected no context");
820 }
821 OptionalContext::NoContext(value) => {
822 assert_eq!(value.len(), 2);
823
824 let short_data_account = value
825 .iter()
826 .find(|acc| acc.pubkey == owned_pubkey_short_data.to_string())
827 .expect("Short data account not found");
828 assert_eq!(short_data_account.account.lamports, 1000);
829
830 let long_data_account = value
831 .iter()
832 .find(|acc| acc.pubkey == owner_pubkey_long_data.to_string())
833 .expect("Long data account not found");
834 assert_eq!(long_data_account.account.lamports, 2000);
835 }
836 }
837 }
838
839 {
841 let res = setup
842 .rpc
843 .get_program_accounts(
844 Some(setup.context.clone()),
845 owner_pubkey.to_string(),
846 Some(RpcProgramAccountsConfig {
847 filters: Some(vec![RpcFilterType::DataSize(3)]),
848 with_context: Some(true),
849 ..Default::default()
850 }),
851 )
852 .await
853 .expect("Failed to get program accounts with data size filter");
854
855 match res {
856 OptionalContext::Context(response) => {
857 assert_eq!(response.value.len(), 1);
858
859 let short_data_account = response
860 .value
861 .iter()
862 .find(|acc| acc.pubkey == owned_pubkey_short_data.to_string())
863 .expect("Short data account not found");
864 assert_eq!(short_data_account.account.lamports, 1000);
865 }
866 OptionalContext::NoContext(_) => {
867 panic!("Expected context");
868 }
869 }
870 }
871
872 {
874 let res = setup
875 .rpc
876 .get_program_accounts(
877 Some(setup.context.clone()),
878 owner_pubkey.to_string(),
879 Some(RpcProgramAccountsConfig {
880 filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes(
881 1,
882 vec![5, 6],
883 ))]),
884 with_context: Some(false),
885 ..Default::default()
886 }),
887 )
888 .await
889 .expect("Failed to get program accounts with memcmp filter");
890
891 match res {
892 OptionalContext::Context(_) => {
893 panic!("Expected no context");
894 }
895 OptionalContext::NoContext(value) => {
896 assert_eq!(value.len(), 1);
897
898 let short_data_account = value
899 .iter()
900 .find(|acc| acc.pubkey == owned_pubkey_short_data.to_string())
901 .expect("Short data account not found");
902 assert_eq!(short_data_account.account.lamports, 1000);
903 }
904 }
905 }
906 }
907
908 #[tokio::test(flavor = "multi_thread")]
909 async fn test_set_and_get_supply() {
910 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
911 let cheatcodes_rpc = SurfnetCheatcodesRpc;
912
913 let initial_supply = setup
915 .rpc
916 .get_supply(Some(setup.context.clone()), None)
917 .await
918 .unwrap();
919
920 assert_eq!(initial_supply.value.total, 0);
921 assert_eq!(initial_supply.value.circulating, 0);
922 assert_eq!(initial_supply.value.non_circulating, 0);
923 assert_eq!(initial_supply.value.non_circulating_accounts.len(), 0);
924
925 let supply_update = SupplyUpdate {
927 total: Some(1_000_000_000_000_000),
928 circulating: Some(800_000_000_000_000),
929 non_circulating: Some(200_000_000_000_000),
930 non_circulating_accounts: Some(vec![
931 VALID_PUBKEY_1.to_string(),
932 VALID_PUBKEY_1.to_string(),
933 ]),
934 };
935
936 let set_result = cheatcodes_rpc
937 .set_supply(Some(setup.context.clone()), supply_update)
938 .await
939 .unwrap();
940
941 assert_eq!(set_result.value, ());
942
943 let supply = setup
945 .rpc
946 .get_supply(Some(setup.context.clone()), None)
947 .await
948 .unwrap();
949
950 assert_eq!(supply.value.total, 1_000_000_000_000_000);
951 assert_eq!(supply.value.circulating, 800_000_000_000_000);
952 assert_eq!(supply.value.non_circulating, 200_000_000_000_000);
953 assert_eq!(supply.value.non_circulating_accounts.len(), 2);
954 assert_eq!(supply.value.non_circulating_accounts[0], VALID_PUBKEY_1);
955 }
956
957 #[tokio::test(flavor = "multi_thread")]
958 async fn test_get_supply_exclude_accounts() {
959 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
960 let cheatcodes_rpc = SurfnetCheatcodesRpc;
961
962 let supply_update = SupplyUpdate {
964 total: Some(1_000_000_000_000_000),
965 circulating: Some(800_000_000_000_000),
966 non_circulating: Some(200_000_000_000_000),
967 non_circulating_accounts: Some(vec![
968 VALID_PUBKEY_1.to_string(),
969 VALID_PUBKEY_1.to_string(),
970 ]),
971 };
972
973 cheatcodes_rpc
974 .set_supply(Some(setup.context.clone()), supply_update)
975 .await
976 .unwrap();
977
978 let config_exclude = RpcSupplyConfig {
980 commitment: None,
981 exclude_non_circulating_accounts_list: true,
982 };
983
984 let supply_excluded = setup
985 .rpc
986 .get_supply(Some(setup.context.clone()), Some(config_exclude))
987 .await
988 .unwrap();
989
990 assert_eq!(supply_excluded.value.total, 1_000_000_000_000_000);
991 assert_eq!(supply_excluded.value.circulating, 800_000_000_000_000);
992 assert_eq!(supply_excluded.value.non_circulating, 200_000_000_000_000);
993 assert_eq!(supply_excluded.value.non_circulating_accounts.len(), 0); let supply_included = setup
997 .rpc
998 .get_supply(Some(setup.context.clone()), None)
999 .await
1000 .unwrap();
1001
1002 assert_eq!(supply_included.value.non_circulating_accounts.len(), 2);
1003 }
1004
1005 #[tokio::test(flavor = "multi_thread")]
1006 async fn test_partial_supply_update() {
1007 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1008 let cheatcodes_rpc = SurfnetCheatcodesRpc;
1009
1010 let initial_update = SupplyUpdate {
1012 total: Some(1_000_000_000_000_000),
1013 circulating: Some(800_000_000_000_000),
1014 non_circulating: Some(200_000_000_000_000),
1015 non_circulating_accounts: Some(vec![VALID_PUBKEY_1.to_string()]),
1016 };
1017
1018 cheatcodes_rpc
1019 .set_supply(Some(setup.context.clone()), initial_update)
1020 .await
1021 .unwrap();
1022
1023 let partial_update = SupplyUpdate {
1025 total: Some(2_000_000_000_000_000),
1026 circulating: None,
1027 non_circulating: None,
1028 non_circulating_accounts: None,
1029 };
1030
1031 cheatcodes_rpc
1032 .set_supply(Some(setup.context.clone()), partial_update)
1033 .await
1034 .unwrap();
1035
1036 let supply = setup
1038 .rpc
1039 .get_supply(Some(setup.context), None)
1040 .await
1041 .unwrap();
1042
1043 assert_eq!(supply.value.total, 2_000_000_000_000_000); assert_eq!(supply.value.circulating, 800_000_000_000_000);
1045 assert_eq!(supply.value.non_circulating, 200_000_000_000_000);
1046 assert_eq!(supply.value.non_circulating_accounts.len(), 1);
1047 }
1048
1049 #[tokio::test(flavor = "multi_thread")]
1050 async fn test_set_supply_with_multiple_invalid_pubkeys() {
1051 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1052 let cheatcodes_rpc = SurfnetCheatcodesRpc;
1053 let invalid_pubkey = "invalid_pubkey";
1054
1055 let supply_update = SupplyUpdate {
1057 total: Some(1_000_000_000_000_000),
1058 circulating: Some(800_000_000_000_000),
1059 non_circulating: Some(200_000_000_000_000),
1060 non_circulating_accounts: Some(vec![
1061 VALID_PUBKEY_1.to_string(), invalid_pubkey.to_string(), "also_invalid".to_string(), ]),
1065 };
1066
1067 let result = cheatcodes_rpc
1068 .set_supply(Some(setup.context), supply_update)
1069 .await;
1070
1071 assert!(result.is_err());
1072 let error = result.unwrap_err();
1073 assert_eq!(error.code, jsonrpc_core::ErrorCode::InvalidParams);
1074 assert_eq!(
1075 error.message,
1076 format!("Invalid pubkey '{}' at index 1", invalid_pubkey)
1077 );
1078 }
1079
1080 #[tokio::test(flavor = "multi_thread")]
1081 async fn test_set_supply_with_max_values() {
1082 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1083 let cheatcodes_rpc = SurfnetCheatcodesRpc;
1084
1085 let supply_update = SupplyUpdate {
1086 total: Some(u64::MAX),
1087 circulating: Some(u64::MAX - 1),
1088 non_circulating: Some(1),
1089 non_circulating_accounts: Some(vec![VALID_PUBKEY_1.to_string()]),
1090 };
1091
1092 let result = cheatcodes_rpc
1093 .set_supply(Some(setup.context.clone()), supply_update)
1094 .await;
1095
1096 assert!(result.is_ok());
1097
1098 let supply = setup
1099 .rpc
1100 .get_supply(Some(setup.context), None)
1101 .await
1102 .unwrap();
1103
1104 assert_eq!(supply.value.total, u64::MAX);
1105 assert_eq!(supply.value.circulating, u64::MAX - 1);
1106 assert_eq!(supply.value.non_circulating, 1);
1107 assert_eq!(supply.value.non_circulating_accounts[0], VALID_PUBKEY_1);
1108 }
1109
1110 #[tokio::test(flavor = "multi_thread")]
1111 async fn test_set_supply_large_valid_account_list() {
1112 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1113 let cheatcodes_rpc = SurfnetCheatcodesRpc;
1114
1115 let large_account_list: Vec<String> = (0..100)
1116 .map(|i| match i % 10 {
1117 0 => "3rSZJHysEk2ueFVovRLtZ8LGnQBMZGg96H2Q4jErspAF".to_string(),
1118 1 => "11111111111111111111111111111111".to_string(),
1119 2 => "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(),
1120 3 => "So11111111111111111111111111111111111111112".to_string(),
1121 4 => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1122 5 => "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB".to_string(),
1123 6 => "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R".to_string(),
1124 7 => "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E".to_string(),
1125 8 => "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk".to_string(),
1126 _ => "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263".to_string(),
1127 })
1128 .collect();
1129
1130 let supply_update = SupplyUpdate {
1131 total: Some(1_000_000_000_000_000),
1132 circulating: Some(800_000_000_000_000),
1133 non_circulating: Some(200_000_000_000_000),
1134 non_circulating_accounts: Some(large_account_list.clone()),
1135 };
1136
1137 let result = cheatcodes_rpc
1138 .set_supply(Some(setup.context.clone()), supply_update)
1139 .await;
1140
1141 assert!(result.is_ok());
1142
1143 let supply = setup
1144 .rpc
1145 .get_supply(Some(setup.context), None)
1146 .await
1147 .unwrap();
1148
1149 assert_eq!(supply.value.non_circulating_accounts.len(), 100);
1150 assert_eq!(
1151 supply.value.non_circulating_accounts[0],
1152 "3rSZJHysEk2ueFVovRLtZ8LGnQBMZGg96H2Q4jErspAF"
1153 );
1154 assert_eq!(
1155 supply.value.non_circulating_accounts[1],
1156 "11111111111111111111111111111111"
1157 );
1158 }
1159
1160 #[tokio::test(flavor = "multi_thread")]
1161 async fn test_get_largest_accounts() {
1162 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1163
1164 let large_circulating_pubkey = Pubkey::new_unique();
1165 let large_circulating_amount = 1_000_000_000_000_000u64;
1166
1167 setup
1168 .context
1169 .svm_locker
1170 .with_svm_writer(|svm_writer| {
1171 svm_writer.set_account(
1172 &large_circulating_pubkey,
1173 Account {
1174 lamports: large_circulating_amount,
1175 ..Default::default()
1176 },
1177 )
1178 })
1179 .unwrap();
1180
1181 let large_non_circulating_pubkey = Pubkey::new_unique();
1182 let large_non_circulating_amount = 2_000_000_000_000_000u64;
1183
1184 setup
1185 .context
1186 .svm_locker
1187 .with_svm_writer(|svm_writer| {
1188 svm_writer
1189 .non_circulating_accounts
1190 .push(large_non_circulating_pubkey.to_string());
1191 svm_writer.set_account(
1192 &large_non_circulating_pubkey,
1193 Account {
1194 lamports: large_non_circulating_amount,
1195 ..Default::default()
1196 },
1197 )
1198 })
1199 .unwrap();
1200
1201 {
1203 let result = setup
1204 .rpc
1205 .get_largest_accounts(
1206 Some(setup.context.clone()),
1207 Some(RpcLargestAccountsConfig {
1208 filter: Some(RpcLargestAccountsFilter::Circulating),
1209 ..Default::default()
1210 }),
1211 )
1212 .await
1213 .unwrap();
1214
1215 assert_eq!(result.value.len(), 1);
1216 assert_eq!(
1217 large_circulating_pubkey.to_string(),
1218 result.value[0].address
1219 );
1220 assert_eq!(large_circulating_amount, result.value[0].lamports);
1221 }
1222
1223 {
1225 let result = setup
1226 .rpc
1227 .get_largest_accounts(
1228 Some(setup.context.clone()),
1229 Some(RpcLargestAccountsConfig {
1230 filter: Some(RpcLargestAccountsFilter::NonCirculating),
1231 ..Default::default()
1232 }),
1233 )
1234 .await
1235 .unwrap();
1236
1237 assert_eq!(result.value.len(), 1);
1238 assert_eq!(
1239 large_non_circulating_pubkey.to_string(),
1240 result.value[0].address
1241 );
1242 assert_eq!(large_non_circulating_amount, result.value[0].lamports);
1243 }
1244
1245 {
1247 let result = setup
1248 .rpc
1249 .get_largest_accounts(Some(setup.context), None)
1250 .await
1251 .unwrap();
1252
1253 assert_eq!(result.value.len(), 2);
1254 assert_eq!(
1255 large_non_circulating_pubkey.to_string(),
1256 result.value[0].address
1257 );
1258 assert_eq!(large_non_circulating_amount, result.value[0].lamports);
1259 assert_eq!(
1260 large_circulating_pubkey.to_string(),
1261 result.value[1].address
1262 );
1263 assert_eq!(large_circulating_amount, result.value[1].lamports);
1264 }
1265 }
1266
1267 #[tokio::test(flavor = "multi_thread")]
1268 async fn test_get_supply_with_invalid_config() {
1269 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1270
1271 let config = RpcSupplyConfig {
1272 commitment: Some(solana_commitment_config::CommitmentConfig {
1273 commitment: solana_commitment_config::CommitmentLevel::Processed,
1274 }),
1275 exclude_non_circulating_accounts_list: false,
1276 };
1277
1278 let result = setup
1279 .rpc
1280 .get_supply(Some(setup.context), Some(config))
1281 .await;
1282
1283 assert!(result.is_ok());
1284 let supply = result.unwrap();
1285 assert_eq!(supply.value.total, 0);
1286 assert_eq!(supply.value.circulating, 0);
1287 assert_eq!(supply.value.non_circulating, 0);
1288 }
1289
1290 #[tokio::test(flavor = "multi_thread")]
1291 async fn test_get_token_largest_accounts_local_svm() {
1292 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1293
1294 let mint_pk = Pubkey::new_unique();
1296 let minimum_rent = setup.context.svm_locker.with_svm_reader(|svm_reader| {
1297 svm_reader
1298 .inner
1299 .minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN)
1300 });
1301
1302 let mut mint_data = [0; spl_token::state::Mint::LEN];
1304 let mint = spl_token::state::Mint {
1305 decimals: 9,
1306 supply: 1000000000000000,
1307 is_initialized: true,
1308 ..Default::default()
1309 };
1310 mint.pack_into_slice(&mut mint_data);
1311
1312 let mint_account = Account {
1313 lamports: minimum_rent,
1314 owner: spl_token::ID,
1315 executable: false,
1316 rent_epoch: 0,
1317 data: mint_data.to_vec(),
1318 };
1319
1320 let token_accounts = vec![
1322 (Pubkey::new_unique(), 1000000000), (Pubkey::new_unique(), 5000000000), (Pubkey::new_unique(), 500000000), (Pubkey::new_unique(), 2000000000), (Pubkey::new_unique(), 100000000), ];
1328
1329 setup.context.svm_locker.with_svm_writer(|svm_writer| {
1330 svm_writer
1332 .set_account(&mint_pk, mint_account.clone())
1333 .unwrap();
1334
1335 for (token_account_pk, amount) in &token_accounts {
1337 let mut token_account_data = [0; TokenAccount::LEN];
1338 let token_account = TokenAccount {
1339 mint: mint_pk,
1340 owner: Pubkey::new_unique(),
1341 amount: *amount,
1342 delegate: solana_sdk::program_option::COption::None,
1343 state: spl_token::state::AccountState::Initialized,
1344 is_native: solana_sdk::program_option::COption::None,
1345 delegated_amount: 0,
1346 close_authority: solana_sdk::program_option::COption::None,
1347 };
1348 token_account.pack_into_slice(&mut token_account_data);
1349
1350 let account = Account {
1351 lamports: minimum_rent,
1352 owner: spl_token::ID,
1353 executable: false,
1354 rent_epoch: 0,
1355 data: token_account_data.to_vec(),
1356 };
1357
1358 svm_writer.set_account(token_account_pk, account).unwrap();
1359 }
1360 });
1361
1362 let result = setup
1363 .rpc
1364 .get_token_largest_accounts(Some(setup.context), mint_pk.to_string(), None)
1365 .await
1366 .unwrap();
1367
1368 assert_eq!(result.value.len(), 5);
1369
1370 assert_eq!(result.value[0].amount.amount, "5000000000"); assert_eq!(result.value[1].amount.amount, "2000000000"); assert_eq!(result.value[2].amount.amount, "1000000000"); assert_eq!(result.value[3].amount.amount, "500000000"); assert_eq!(result.value[4].amount.amount, "100000000"); for balance in &result.value {
1379 assert_eq!(balance.amount.decimals, 9);
1380 assert!(balance.amount.ui_amount.is_some());
1381 assert!(!balance.amount.ui_amount_string.is_empty());
1382 }
1383
1384 for balance in &result.value {
1386 assert!(Pubkey::from_str(&balance.address).is_ok());
1387 }
1388 }
1389
1390 #[tokio::test(flavor = "multi_thread")]
1391 async fn test_get_token_largest_accounts_limit_to_20() {
1392 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1393
1394 let mint_pk = Pubkey::new_unique();
1396 let minimum_rent = setup.context.svm_locker.with_svm_reader(|svm_reader| {
1397 svm_reader
1398 .inner
1399 .minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN)
1400 });
1401
1402 let mut mint_data = [0; spl_token::state::Mint::LEN];
1404 let mint = spl_token::state::Mint {
1405 decimals: 6,
1406 supply: 1000000000000000,
1407 is_initialized: true,
1408 ..Default::default()
1409 };
1410 mint.pack_into_slice(&mut mint_data);
1411
1412 let mint_account = Account {
1413 lamports: minimum_rent,
1414 owner: spl_token::ID,
1415 executable: false,
1416 rent_epoch: 0,
1417 data: mint_data.to_vec(),
1418 };
1419
1420 let mut token_accounts = Vec::new();
1422 for i in 0..25 {
1423 token_accounts.push((Pubkey::new_unique(), (i + 1) * 1000000)); }
1425
1426 setup.context.svm_locker.with_svm_writer(|svm_writer| {
1427 svm_writer
1429 .set_account(&mint_pk, mint_account.clone())
1430 .unwrap();
1431
1432 for (token_account_pk, amount) in &token_accounts {
1434 let mut token_account_data = [0; TokenAccount::LEN];
1435 let token_account = TokenAccount {
1436 mint: mint_pk,
1437 owner: Pubkey::new_unique(),
1438 amount: *amount,
1439 delegate: solana_sdk::program_option::COption::None,
1440 state: spl_token::state::AccountState::Initialized,
1441 is_native: solana_sdk::program_option::COption::None,
1442 delegated_amount: 0,
1443 close_authority: solana_sdk::program_option::COption::None,
1444 };
1445 token_account.pack_into_slice(&mut token_account_data);
1446
1447 let account = Account {
1448 lamports: minimum_rent,
1449 owner: spl_token::ID,
1450 executable: false,
1451 rent_epoch: 0,
1452 data: token_account_data.to_vec(),
1453 };
1454
1455 svm_writer.set_account(token_account_pk, account).unwrap();
1456 }
1457 });
1458
1459 let result = setup
1461 .rpc
1462 .get_token_largest_accounts(Some(setup.context), mint_pk.to_string(), None)
1463 .await
1464 .unwrap();
1465
1466 assert_eq!(result.value.len(), 20);
1468
1469 assert_eq!(result.value[0].amount.amount, "25000000"); assert_eq!(result.value[1].amount.amount, "24000000"); assert_eq!(result.value[19].amount.amount, "6000000"); for balance in &result.value {
1476 assert_eq!(balance.amount.decimals, 6);
1477 assert!(balance.amount.ui_amount.is_some());
1478 assert!(!balance.amount.ui_amount_string.is_empty());
1479 assert!(Pubkey::from_str(&balance.address).is_ok());
1480 }
1481 }
1482
1483 #[tokio::test(flavor = "multi_thread")]
1484 async fn test_get_token_largest_accounts_edge_cases() {
1485 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1486
1487 let invalid_result = setup
1489 .rpc
1490 .get_token_largest_accounts(
1491 Some(setup.context.clone()),
1492 "invalid_pubkey".to_string(),
1493 None,
1494 )
1495 .await;
1496 assert!(invalid_result.is_err());
1497 let error = invalid_result.unwrap_err();
1498 assert_eq!(error.code, jsonrpc_core::ErrorCode::InvalidParams);
1499
1500 let empty_mint_pk = Pubkey::new_unique();
1502 let minimum_rent = setup.context.svm_locker.with_svm_reader(|svm_reader| {
1503 svm_reader
1504 .inner
1505 .minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN)
1506 });
1507
1508 let mut mint_data = [0; spl_token::state::Mint::LEN];
1510 let mint = spl_token::state::Mint {
1511 decimals: 9,
1512 supply: 0,
1513 is_initialized: true,
1514 ..Default::default()
1515 };
1516 mint.pack_into_slice(&mut mint_data);
1517
1518 let mint_account = Account {
1519 lamports: minimum_rent,
1520 owner: spl_token::ID,
1521 executable: false,
1522 rent_epoch: 0,
1523 data: mint_data.to_vec(),
1524 };
1525
1526 setup.context.svm_locker.with_svm_writer(|svm_writer| {
1527 svm_writer
1528 .set_account(&empty_mint_pk, mint_account.clone())
1529 .unwrap();
1530 });
1531
1532 let empty_result = setup
1533 .rpc
1534 .get_token_largest_accounts(
1535 Some(setup.context.clone()),
1536 empty_mint_pk.to_string(),
1537 None,
1538 )
1539 .await
1540 .unwrap();
1541
1542 assert_eq!(empty_result.value.len(), 0);
1544
1545 let nonexistent_mint_pk = Pubkey::new_unique();
1547 let nonexistent_result = setup
1548 .rpc
1549 .get_token_largest_accounts(Some(setup.context), nonexistent_mint_pk.to_string(), None)
1550 .await
1551 .unwrap();
1552
1553 assert_eq!(nonexistent_result.value.len(), 0);
1555 }
1556
1557 #[tokio::test(flavor = "multi_thread")]
1558 async fn test_get_token_accounts_by_delegate() {
1559 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1560
1561 let delegate = Pubkey::new_unique();
1562 let owner = Pubkey::new_unique();
1563 let mint = Pubkey::new_unique();
1564 let token_account_pubkey = Pubkey::new_unique();
1565 let token_program = spl_token::id();
1566
1567 let mut token_account_data = [0u8; spl_token::state::Account::LEN];
1569 let token_account = spl_token::state::Account {
1570 mint,
1571 owner,
1572 amount: 1000,
1573 delegate: solana_sdk::program_option::COption::Some(delegate),
1574 state: spl_token::state::AccountState::Initialized,
1575 is_native: solana_sdk::program_option::COption::None,
1576 delegated_amount: 500,
1577 close_authority: solana_sdk::program_option::COption::None,
1578 };
1579 solana_sdk::program_pack::Pack::pack_into_slice(&token_account, &mut token_account_data);
1580
1581 let account = Account {
1582 lamports: 1000000,
1583 data: token_account_data.to_vec(),
1584 owner: token_program,
1585 executable: false,
1586 rent_epoch: 0,
1587 };
1588
1589 setup.context.svm_locker.with_svm_writer(|svm_writer| {
1590 svm_writer
1591 .set_account(&token_account_pubkey, account.clone())
1592 .unwrap();
1593 });
1594
1595 let result = setup
1597 .rpc
1598 .get_token_accounts_by_delegate(
1599 Some(setup.context.clone()),
1600 delegate.to_string(),
1601 RpcTokenAccountsFilter::ProgramId(token_program.to_string()),
1602 None,
1603 )
1604 .await;
1605
1606 assert!(result.is_ok(), "ProgramId filter should succeed");
1607 let response = result.unwrap();
1608 assert_eq!(response.value.len(), 1, "Should find 1 token account");
1609 assert_eq!(response.value[0].pubkey, token_account_pubkey.to_string());
1610
1611 let result = setup
1613 .rpc
1614 .get_token_accounts_by_delegate(
1615 Some(setup.context.clone()),
1616 delegate.to_string(),
1617 RpcTokenAccountsFilter::Mint(mint.to_string()),
1618 None,
1619 )
1620 .await;
1621
1622 assert!(result.is_ok(), "Mint filter should succeed");
1623 let response = result.unwrap();
1624 assert_eq!(response.value.len(), 1, "Should find 1 token account");
1625 assert_eq!(response.value[0].pubkey, token_account_pubkey.to_string());
1626
1627 let non_existent_delegate = Pubkey::new_unique();
1629 let result = setup
1630 .rpc
1631 .get_token_accounts_by_delegate(
1632 Some(setup.context.clone()),
1633 non_existent_delegate.to_string(),
1634 RpcTokenAccountsFilter::ProgramId(token_program.to_string()),
1635 None,
1636 )
1637 .await;
1638
1639 assert!(result.is_ok(), "Non-existent delegate should succeed");
1640 let response = result.unwrap();
1641 assert_eq!(response.value.len(), 0, "Should find 0 token accounts");
1642
1643 let wrong_mint = Pubkey::new_unique();
1645 let result = setup
1646 .rpc
1647 .get_token_accounts_by_delegate(
1648 Some(setup.context.clone()),
1649 delegate.to_string(),
1650 RpcTokenAccountsFilter::Mint(wrong_mint.to_string()),
1651 None,
1652 )
1653 .await;
1654
1655 assert!(result.is_ok(), "Wrong mint should succeed");
1656 let response = result.unwrap();
1657 assert_eq!(response.value.len(), 0, "Should find 0 token accounts");
1658
1659 let result = setup
1661 .rpc
1662 .get_token_accounts_by_delegate(
1663 Some(setup.context.clone()),
1664 "invalid_pubkey".to_string(),
1665 RpcTokenAccountsFilter::ProgramId(token_program.to_string()),
1666 None,
1667 )
1668 .await;
1669
1670 assert!(result.is_err(), "Invalid pubkey should fail");
1671 }
1672
1673 #[tokio::test(flavor = "multi_thread")]
1674 async fn test_get_token_accounts_by_delegate_multiple_accounts() {
1675 let setup = TestSetup::new(SurfpoolAccountsScanRpc);
1676
1677 let delegate = Pubkey::new_unique();
1678 let owner1 = Pubkey::new_unique();
1679 let owner2 = Pubkey::new_unique();
1680 let mint1 = Pubkey::new_unique();
1681 let mint2 = Pubkey::new_unique();
1682 let token_account1 = Pubkey::new_unique();
1683 let token_account2 = Pubkey::new_unique();
1684 let token_program = spl_token::id();
1685
1686 let mut token_account_data1 = [0u8; spl_token::state::Account::LEN];
1688 let token_account_struct1 = spl_token::state::Account {
1689 mint: mint1,
1690 owner: owner1,
1691 amount: 1000,
1692 delegate: solana_sdk::program_option::COption::Some(delegate),
1693 state: spl_token::state::AccountState::Initialized,
1694 is_native: solana_sdk::program_option::COption::None,
1695 delegated_amount: 500,
1696 close_authority: solana_sdk::program_option::COption::None,
1697 };
1698 solana_sdk::program_pack::Pack::pack_into_slice(
1699 &token_account_struct1,
1700 &mut token_account_data1,
1701 );
1702
1703 let mut token_account_data2 = [0u8; spl_token::state::Account::LEN];
1705 let token_account_struct2 = spl_token::state::Account {
1706 mint: mint2,
1707 owner: owner2,
1708 amount: 2000,
1709 delegate: solana_sdk::program_option::COption::Some(delegate),
1710 state: spl_token::state::AccountState::Initialized,
1711 is_native: solana_sdk::program_option::COption::None,
1712 delegated_amount: 1000,
1713 close_authority: solana_sdk::program_option::COption::None,
1714 };
1715 solana_sdk::program_pack::Pack::pack_into_slice(
1716 &token_account_struct2,
1717 &mut token_account_data2,
1718 );
1719
1720 setup.context.svm_locker.with_svm_writer(|svm_writer| {
1721 svm_writer
1722 .set_account(
1723 &token_account1,
1724 Account {
1725 lamports: 1000000,
1726 data: token_account_data1.to_vec(),
1727 owner: token_program,
1728 executable: false,
1729 rent_epoch: 0,
1730 },
1731 )
1732 .unwrap();
1733
1734 svm_writer
1735 .set_account(
1736 &token_account2,
1737 Account {
1738 lamports: 1000000,
1739 data: token_account_data2.to_vec(),
1740 owner: token_program,
1741 executable: false,
1742 rent_epoch: 0,
1743 },
1744 )
1745 .unwrap();
1746 });
1747
1748 let result = setup
1749 .rpc
1750 .get_token_accounts_by_delegate(
1751 Some(setup.context.clone()),
1752 delegate.to_string(),
1753 RpcTokenAccountsFilter::ProgramId(token_program.to_string()),
1754 None,
1755 )
1756 .await;
1757
1758 assert!(result.is_ok(), "ProgramId filter should succeed");
1759 let response = result.unwrap();
1760 assert_eq!(response.value.len(), 2, "Should find 2 token accounts");
1761
1762 let returned_pubkeys: std::collections::HashSet<String> = response
1763 .value
1764 .iter()
1765 .map(|acc| acc.pubkey.clone())
1766 .collect();
1767 assert!(returned_pubkeys.contains(&token_account1.to_string()));
1768 assert!(returned_pubkeys.contains(&token_account2.to_string()));
1769
1770 let result = setup
1772 .rpc
1773 .get_token_accounts_by_delegate(
1774 Some(setup.context.clone()),
1775 delegate.to_string(),
1776 RpcTokenAccountsFilter::Mint(mint1.to_string()),
1777 None,
1778 )
1779 .await;
1780
1781 assert!(result.is_ok(), "Mint filter should succeed");
1782 let response = result.unwrap();
1783 assert_eq!(
1784 response.value.len(),
1785 1,
1786 "Should find 1 token account for mint1"
1787 );
1788 assert_eq!(response.value[0].pubkey, token_account1.to_string());
1789 }
1790}