1use std::{
2 collections::{BTreeMap, HashSet},
3 sync::Arc,
4};
5
6use bincode::serialized_size;
7use crossbeam_channel::{Receiver, Sender};
8use itertools::Itertools;
9use litesvm::types::{FailedTransactionMetadata, SimulatedTransactionInfo, TransactionResult};
10use solana_account::{Account, ReadableAccount};
11use solana_account_decoder::{
12 UiAccount, UiAccountEncoding,
13 parse_bpf_loader::{BpfUpgradeableLoaderAccountType, UiProgram, parse_bpf_upgradeable_loader},
14 parse_token::UiTokenAmount,
15};
16use solana_address_lookup_table_interface::state::AddressLookupTable;
17use solana_client::{
18 rpc_config::{
19 RpcAccountInfoConfig, RpcBlockConfig, RpcLargestAccountsConfig, RpcLargestAccountsFilter,
20 RpcSignaturesForAddressConfig, RpcTransactionConfig, RpcTransactionLogsFilter,
21 },
22 rpc_filter::RpcFilterType,
23 rpc_request::TokenAccountsFilter,
24 rpc_response::{
25 RpcAccountBalance, RpcConfirmedTransactionStatusWithSignature, RpcKeyedAccount,
26 RpcLogsResponse, RpcTokenAccountBalance,
27 },
28};
29use solana_clock::Slot;
30use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
31use solana_epoch_info::EpochInfo;
32use solana_hash::Hash;
33use solana_message::{
34 SimpleAddressLoader, VersionedMessage,
35 v0::{LoadedAddresses, MessageAddressTableLookup},
36};
37use solana_pubkey::Pubkey;
38use solana_rpc_client_api::response::SlotInfo;
39use solana_sdk::{
40 bpf_loader_upgradeable::{UpgradeableLoaderState, get_program_data_address},
41 transaction::{SanitizedTransaction, VersionedTransaction},
42};
43use solana_signature::Signature;
44use solana_transaction_error::TransactionError;
45use solana_transaction_status::{
46 EncodedConfirmedTransactionWithStatusMeta,
47 TransactionConfirmationStatus as SolanaTransactionConfirmationStatus, UiConfirmedBlock,
48 UiTransactionEncoding,
49};
50use surfpool_types::{
51 ComputeUnitsEstimationResult, ProfileResult, ProfileState, SimnetEvent,
52 TransactionConfirmationStatus, TransactionStatusEvent, UuidOrSignature,
53};
54use tokio::sync::RwLock;
55use uuid::Uuid;
56
57use super::{
58 AccountFactory, GetAccountResult, GetTransactionResult, GeyserEvent, SignatureSubscriptionType,
59 SurfnetSvm,
60 remote::{SomeRemoteCtx, SurfnetRemoteClient},
61};
62use crate::{
63 error::{SurfpoolError, SurfpoolResult},
64 rpc::utils::{convert_transaction_metadata_from_canonical, verify_pubkey},
65 surfnet::FINALIZATION_SLOT_THRESHOLD,
66 types::{
67 GeyserAccountUpdate, RemoteRpcResult, SurfnetTransactionStatus, TransactionWithStatusMeta,
68 },
69};
70
71pub struct SvmAccessContext<T> {
72 pub slot: Slot,
73 pub latest_epoch_info: EpochInfo,
74 pub latest_blockhash: Hash,
75 pub inner: T,
76}
77
78impl<T> SvmAccessContext<T> {
79 pub fn new(slot: Slot, latest_epoch_info: EpochInfo, latest_blockhash: Hash, inner: T) -> Self {
80 Self {
81 slot,
82 latest_blockhash,
83 latest_epoch_info,
84 inner,
85 }
86 }
87
88 pub fn inner(&self) -> &T {
89 &self.inner
90 }
91
92 pub fn with_new_value<N>(&self, inner: N) -> SvmAccessContext<N> {
93 SvmAccessContext {
94 slot: self.slot,
95 latest_blockhash: self.latest_blockhash,
96 latest_epoch_info: self.latest_epoch_info.clone(),
97 inner,
98 }
99 }
100}
101
102pub type SurfpoolContextualizedResult<T> = SurfpoolResult<SvmAccessContext<T>>;
103
104pub struct SurfnetSvmLocker(pub Arc<RwLock<SurfnetSvm>>);
105
106impl Clone for SurfnetSvmLocker {
107 fn clone(&self) -> Self {
108 Self(self.0.clone())
109 }
110}
111
112impl SurfnetSvmLocker {
114 pub fn with_svm_reader<T, F>(&self, reader: F) -> T
120 where
121 F: Fn(&SurfnetSvm) -> T + Send + Sync,
122 {
123 let read_lock = self.0.clone();
124 tokio::task::block_in_place(move || {
125 let read_guard = read_lock.blocking_read();
126 reader(&read_guard)
127 })
128 }
129
130 fn with_contextualized_svm_reader<T, F>(&self, reader: F) -> SvmAccessContext<T>
133 where
134 F: Fn(&SurfnetSvm) -> T + Send + Sync,
135 T: Send + 'static,
136 {
137 let read_lock = self.0.clone();
138 tokio::task::block_in_place(move || {
139 let read_guard = read_lock.blocking_read();
140 let res = reader(&read_guard);
141
142 SvmAccessContext::new(
143 read_guard.get_latest_absolute_slot(),
144 read_guard.latest_epoch_info(),
145 read_guard.latest_blockhash(),
146 res,
147 )
148 })
149 }
150
151 pub fn with_svm_writer<T, F>(&self, writer: F) -> T
157 where
158 F: Fn(&mut SurfnetSvm) -> T + Send + Sync,
159 T: Send + 'static,
160 {
161 let write_lock = self.0.clone();
162 tokio::task::block_in_place(move || {
163 let mut write_guard = write_lock.blocking_write();
164 writer(&mut write_guard)
165 })
166 }
167}
168
169impl SurfnetSvmLocker {
171 pub fn new(svm: SurfnetSvm) -> Self {
173 Self(Arc::new(RwLock::new(svm)))
174 }
175
176 pub async fn initialize(
179 &self,
180 remote_ctx: &Option<SurfnetRemoteClient>,
181 ) -> SurfpoolResult<EpochInfo> {
182 let epoch_info = if let Some(remote_client) = remote_ctx {
183 remote_client.get_epoch_info().await?
184 } else {
185 EpochInfo {
186 epoch: 0,
187 slot_index: 0,
188 slots_in_epoch: 0,
189 absolute_slot: 0,
190 block_height: 0,
191 transaction_count: None,
192 }
193 };
194
195 self.with_svm_writer(|svm_writer| {
196 svm_writer.initialize(epoch_info.clone(), remote_ctx);
197 });
198 Ok(epoch_info)
199 }
200}
201
202impl SurfnetSvmLocker {
204 pub fn get_account_local(&self, pubkey: &Pubkey) -> SvmAccessContext<GetAccountResult> {
206 self.with_contextualized_svm_reader(|svm_reader| {
207 match svm_reader.inner.get_account(pubkey) {
208 Some(account) => GetAccountResult::FoundAccount(
209 *pubkey, account,
210 false,
212 ),
213 None => GetAccountResult::None(*pubkey),
214 }
215 })
216 }
217
218 pub async fn get_account_local_then_remote(
220 &self,
221 client: &SurfnetRemoteClient,
222 pubkey: &Pubkey,
223 commitment_config: CommitmentConfig,
224 ) -> SurfpoolContextualizedResult<GetAccountResult> {
225 let result = self.get_account_local(pubkey);
226
227 if result.inner.is_none() {
228 let remote_account = client.get_account(pubkey, commitment_config).await?;
229 Ok(result.with_new_value(remote_account))
230 } else {
231 Ok(result)
232 }
233 }
234
235 pub async fn get_account(
237 &self,
238 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
239 pubkey: &Pubkey,
240 factory: Option<AccountFactory>,
241 ) -> SurfpoolContextualizedResult<GetAccountResult> {
242 let result = if let Some((remote_client, commitment_config)) = remote_ctx {
243 self.get_account_local_then_remote(remote_client, pubkey, *commitment_config)
244 .await?
245 } else {
246 self.get_account_local(pubkey)
247 };
248
249 match (&result.inner, factory) {
250 (&GetAccountResult::None(_), Some(factory)) => {
251 let default = factory(self.clone());
252 Ok(result.with_new_value(default))
253 }
254 _ => Ok(result),
255 }
256 }
257
258 pub fn get_multiple_accounts_local(
260 &self,
261 pubkeys: &[Pubkey],
262 ) -> SvmAccessContext<Vec<GetAccountResult>> {
263 self.with_contextualized_svm_reader(|svm_reader| {
264 let mut accounts = vec![];
265
266 for pubkey in pubkeys {
267 let res = match svm_reader.inner.get_account(pubkey) {
268 Some(account) => GetAccountResult::FoundAccount(
269 *pubkey, account,
270 false,
272 ),
273 None => GetAccountResult::None(*pubkey),
274 };
275 accounts.push(res);
276 }
277 accounts
278 })
279 }
280
281 pub async fn get_multiple_accounts_local_then_remote(
283 &self,
284 client: &SurfnetRemoteClient,
285 pubkeys: &[Pubkey],
286 commitment_config: CommitmentConfig,
287 ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
288 let SvmAccessContext {
289 slot,
290 latest_epoch_info,
291 latest_blockhash,
292 inner: local_results,
293 } = self.get_multiple_accounts_local(pubkeys);
294
295 let mut missing_accounts = vec![];
296 let mut found_accounts = vec![];
297 for result in local_results.into_iter() {
298 if let GetAccountResult::None(pubkey) = result {
299 missing_accounts.push(pubkey)
300 } else {
301 found_accounts.push(result.clone());
302 }
303 }
304
305 if missing_accounts.is_empty() {
306 return Ok(SvmAccessContext::new(
307 slot,
308 latest_epoch_info,
309 latest_blockhash,
310 found_accounts,
311 ));
312 }
313
314 let mut remote_results = client
315 .get_multiple_accounts(&missing_accounts, commitment_config)
316 .await?;
317 let mut combined_results = found_accounts.clone();
318 combined_results.append(&mut remote_results);
319
320 Ok(SvmAccessContext::new(
321 slot,
322 latest_epoch_info,
323 latest_blockhash,
324 combined_results,
325 ))
326 }
327
328 pub async fn get_multiple_accounts(
330 &self,
331 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
332 pubkeys: &[Pubkey],
333 factory: Option<AccountFactory>,
334 ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
335 let results = if let Some((remote_client, commitment_config)) = remote_ctx {
336 self.get_multiple_accounts_local_then_remote(remote_client, pubkeys, *commitment_config)
337 .await?
338 } else {
339 self.get_multiple_accounts_local(pubkeys)
340 };
341
342 let mut combined = Vec::with_capacity(results.inner.len());
343 for result in results.inner.clone() {
344 match (&result, &factory) {
345 (&GetAccountResult::None(_), Some(factory)) => {
346 let default = factory(self.clone());
347 combined.push(default);
348 }
349 _ => combined.push(result),
350 }
351 }
352 Ok(results.with_new_value(combined))
353 }
354
355 pub fn get_largest_accounts_local(
357 &self,
358 config: RpcLargestAccountsConfig,
359 ) -> SvmAccessContext<Vec<RpcAccountBalance>> {
360 self.with_contextualized_svm_reader(|svm_reader| {
361 let non_circulating_accounts: Vec<_> = svm_reader
362 .non_circulating_accounts
363 .iter()
364 .flat_map(|acct| verify_pubkey(acct))
365 .collect();
366
367 let ordered_accounts = svm_reader
368 .accounts_registry
369 .iter()
370 .sorted_by(|a, b| b.1.lamports.cmp(&a.1.lamports))
371 .collect::<Vec<_>>();
372 let ordered_filtered_accounts = match config.filter {
373 Some(RpcLargestAccountsFilter::NonCirculating) => ordered_accounts
374 .into_iter()
375 .filter(|(pubkey, _)| non_circulating_accounts.contains(pubkey))
376 .collect::<Vec<_>>(),
377 Some(RpcLargestAccountsFilter::Circulating) => ordered_accounts
378 .into_iter()
379 .filter(|(pubkey, _)| !non_circulating_accounts.contains(pubkey))
380 .collect::<Vec<_>>(),
381 None => ordered_accounts,
382 };
383
384 ordered_filtered_accounts
385 .iter()
386 .take(20)
387 .map(|(pubkey, account)| RpcAccountBalance {
388 address: pubkey.to_string(),
389 lamports: account.lamports,
390 })
391 .collect()
392 })
393 }
394
395 pub async fn get_largest_accounts_local_then_remote(
396 &self,
397 client: &SurfnetRemoteClient,
398 config: RpcLargestAccountsConfig,
399 commitment_config: CommitmentConfig,
400 ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
401 {
404 let remote_non_circulating_pubkeys_result = client
405 .get_largest_accounts(Some(RpcLargestAccountsConfig {
406 filter: Some(RpcLargestAccountsFilter::NonCirculating),
407 ..config.clone()
408 }))
409 .await?;
410
411 let (mut remote_non_circulating_pubkeys, mut remote_circulating_pubkeys) =
412 match remote_non_circulating_pubkeys_result {
413 RemoteRpcResult::Ok(non_circulating_accounts) => {
414 let remote_circulating_pubkeys_result = client
415 .get_largest_accounts(Some(RpcLargestAccountsConfig {
416 filter: Some(RpcLargestAccountsFilter::Circulating),
417 ..config.clone()
418 }))
419 .await?;
420
421 let remote_circulating_pubkeys = match remote_circulating_pubkeys_result {
422 RemoteRpcResult::Ok(circulating_accounts) => circulating_accounts,
423 RemoteRpcResult::MethodNotSupported => {
424 unreachable!()
425 }
426 };
427 (
428 non_circulating_accounts
429 .iter()
430 .map(|account_balance| verify_pubkey(&account_balance.address))
431 .collect::<SurfpoolResult<Vec<_>>>()?,
432 remote_circulating_pubkeys
433 .iter()
434 .map(|account_balance| verify_pubkey(&account_balance.address))
435 .collect::<SurfpoolResult<Vec<_>>>()?,
436 )
437 }
438 RemoteRpcResult::MethodNotSupported => {
439 let tx = self.simnet_events_tx();
440 let _ = tx.send(SimnetEvent::warn("The `getLargestAccounts` method was sent to the remote RPC, but this method isn't supported by your RPC provider. Only local accounts will be returned."));
441 (vec![], vec![])
442 }
443 };
444
445 let mut combined = Vec::with_capacity(
446 remote_non_circulating_pubkeys.len() + remote_circulating_pubkeys.len(),
447 );
448 combined.append(&mut remote_non_circulating_pubkeys);
449 combined.append(&mut remote_circulating_pubkeys);
450
451 let get_account_results = self
452 .get_multiple_accounts_local_then_remote(client, &combined, commitment_config)
453 .await?
454 .inner;
455
456 self.write_multiple_account_updates(&get_account_results);
457 }
458
459 Ok(self.get_largest_accounts_local(config))
462 }
463
464 pub async fn get_largest_accounts(
465 &self,
466 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
467 config: RpcLargestAccountsConfig,
468 ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
469 let results = if let Some((remote_client, commitment_config)) = remote_ctx {
470 self.get_largest_accounts_local_then_remote(remote_client, config, *commitment_config)
471 .await?
472 } else {
473 self.get_largest_accounts_local(config)
474 };
475
476 Ok(results)
477 }
478
479 pub fn account_to_rpc_keyed_account<T: ReadableAccount + Send + Sync>(
480 &self,
481 pubkey: &Pubkey,
482 account: &T,
483 config: &RpcAccountInfoConfig,
484 token_mint: Option<Pubkey>,
485 ) -> RpcKeyedAccount {
486 self.with_svm_reader(|svm_reader| {
487 svm_reader.account_to_rpc_keyed_account(pubkey, account, config, token_mint)
488 })
489 }
490}
491
492impl SurfnetSvmLocker {
494 pub fn get_signatures_for_address_local(
495 &self,
496 pubkey: &Pubkey,
497 config: Option<RpcSignaturesForAddressConfig>,
498 ) -> SvmAccessContext<Vec<RpcConfirmedTransactionStatusWithSignature>> {
499 self.with_contextualized_svm_reader(|svm_reader| {
500 let current_slot = svm_reader.get_latest_absolute_slot();
501
502 let config = config.clone().unwrap_or_default();
503 let limit = config.limit.unwrap_or(1000);
504
505 let mut before_slot = None;
506 let mut until_slot = None;
507
508 let sigs: Vec<_> = svm_reader
509 .transactions
510 .iter()
511 .filter_map(|(sig, status)| {
512 let TransactionWithStatusMeta {
513 slot,
514 transaction,
515 meta,
516 } = status.expect_processed();
517
518 if *slot < config.clone().min_context_slot.unwrap_or_default() {
519 return None;
520 }
521
522 if Some(sig.to_string()) == config.clone().before {
523 before_slot = Some(*slot)
524 }
525
526 if Some(sig.to_string()) == config.clone().until {
527 until_slot = Some(*slot)
528 }
529
530 let is_signer = transaction
532 .message
533 .static_account_keys()
534 .iter()
535 .position(|pk| pk == pubkey)
536 .map(|i| transaction.message.is_signer(i))
537 .unwrap_or(false);
538
539 if !is_signer {
540 return None;
541 }
542
543 let confirmation_status = match current_slot {
545 cs if cs == *slot => SolanaTransactionConfirmationStatus::Processed,
546 cs if cs < slot + FINALIZATION_SLOT_THRESHOLD => {
547 SolanaTransactionConfirmationStatus::Confirmed
548 }
549 _ => SolanaTransactionConfirmationStatus::Finalized,
550 };
551
552 Some(RpcConfirmedTransactionStatusWithSignature {
553 err: match &meta.status {
554 Ok(_) => None,
555 Err(e) => Some(e.clone()),
556 },
557 slot: *slot,
558 memo: None,
559 block_time: None,
560 confirmation_status: Some(confirmation_status),
561 signature: sig.to_string(),
562 })
563 })
564 .collect();
565
566 sigs.into_iter()
567 .filter(|sig| {
568 if config.before.is_none() && config.until.is_none() {
569 return true;
570 }
571
572 if config.before.is_some() && before_slot >= Some(sig.slot) {
573 return true;
574 }
575
576 if config.until.is_some() && until_slot <= Some(sig.slot) {
577 return true;
578 }
579
580 false
581 })
582 .take(limit)
583 .collect()
584 })
585 }
586
587 pub async fn get_signatures_for_address_local_then_remote(
588 &self,
589 client: &SurfnetRemoteClient,
590 pubkey: &Pubkey,
591 config: Option<RpcSignaturesForAddressConfig>,
592 ) -> SurfpoolContextualizedResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
593 let results = self.get_signatures_for_address_local(pubkey, config.clone());
594 let limit = config.clone().and_then(|c| c.limit).unwrap_or(1000);
595
596 let mut combined_results = results.inner.clone();
597 if combined_results.len() < limit {
598 let mut remote_results = client.get_signatures_for_address(pubkey, config).await?;
599 combined_results.append(&mut remote_results);
600 }
601
602 Ok(results.with_new_value(combined_results))
603 }
604
605 pub async fn get_signatures_for_address(
606 &self,
607 remote_ctx: &Option<(SurfnetRemoteClient, ())>,
608 pubkey: &Pubkey,
609 config: Option<RpcSignaturesForAddressConfig>,
610 ) -> SurfpoolContextualizedResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
611 let results = if let Some((remote_client, _)) = remote_ctx {
612 self.get_signatures_for_address_local_then_remote(remote_client, pubkey, config.clone())
613 .await?
614 } else {
615 self.get_signatures_for_address_local(pubkey, config)
616 };
617
618 Ok(results)
619 }
620}
621
622impl SurfnetSvmLocker {
624 pub async fn get_transaction(
626 &self,
627 remote_ctx: &Option<SurfnetRemoteClient>,
628 signature: &Signature,
629 config: RpcTransactionConfig,
630 ) -> SurfpoolResult<GetTransactionResult> {
631 if let Some(remote_client) = remote_ctx {
632 self.get_transaction_local_then_remote(remote_client, signature, config)
633 .await
634 } else {
635 self.get_transaction_local(signature, &config)
636 }
637 }
638
639 pub fn get_transaction_local(
641 &self,
642 signature: &Signature,
643 config: &RpcTransactionConfig,
644 ) -> SurfpoolResult<GetTransactionResult> {
645 self.with_svm_reader(|svm_reader| {
646 let latest_absolute_slot = svm_reader.get_latest_absolute_slot();
647
648 let Some(entry) = svm_reader.transactions.get(signature) else {
649 return Ok(GetTransactionResult::None(*signature));
650 };
651
652 let transaction_with_status_meta = entry.expect_processed();
653 let slot = transaction_with_status_meta.slot;
654 let block_time = svm_reader
655 .blocks
656 .get(&slot)
657 .map(|b| b.block_time)
658 .unwrap_or(0);
659 let encoded = transaction_with_status_meta.encode(
660 config.encoding.unwrap_or(UiTransactionEncoding::JsonParsed),
661 config.max_supported_transaction_version,
662 true,
663 )?;
664 Ok(GetTransactionResult::found_transaction(
665 *signature,
666 EncodedConfirmedTransactionWithStatusMeta {
667 slot,
668 transaction: encoded,
669 block_time: Some(block_time),
670 },
671 latest_absolute_slot,
672 ))
673 })
674 }
675
676 pub async fn get_transaction_local_then_remote(
678 &self,
679 client: &SurfnetRemoteClient,
680 signature: &Signature,
681 config: RpcTransactionConfig,
682 ) -> SurfpoolResult<GetTransactionResult> {
683 let local_result = self.get_transaction_local(signature, &config)?;
684 let latest_absolute_slot = self.get_latest_absolute_slot();
685 if local_result.is_none() {
686 Ok(client
687 .get_transaction(*signature, config, latest_absolute_slot)
688 .await)
689 } else {
690 Ok(local_result)
691 }
692 }
693}
694
695impl SurfnetSvmLocker {
697 pub fn simulate_transaction(
699 &self,
700 transaction: VersionedTransaction,
701 sigverify: bool,
702 ) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata> {
703 self.with_svm_reader(|svm_reader| {
704 svm_reader.simulate_transaction(transaction.clone(), sigverify)
705 })
706 }
707
708 pub async fn process_transaction(
710 &self,
711 remote_ctx: &Option<SurfnetRemoteClient>,
712 transaction: VersionedTransaction,
713 status_tx: Sender<TransactionStatusEvent>,
714 skip_preflight: bool,
715 ) -> SurfpoolContextualizedResult<()> {
716 let remote_ctx = &remote_ctx.get_remote_ctx(CommitmentConfig::confirmed());
717 let (latest_absolute_slot, latest_epoch_info, latest_blockhash) =
718 self.with_svm_writer(|svm_writer| {
719 let latest_absolute_slot = svm_writer.get_latest_absolute_slot();
720 svm_writer.notify_signature_subscribers(
721 SignatureSubscriptionType::received(),
722 &transaction.signatures[0],
723 latest_absolute_slot,
724 None,
725 );
726 (
727 latest_absolute_slot,
728 svm_writer.latest_epoch_info(),
729 svm_writer.latest_blockhash(),
730 )
731 });
732
733 let signature = transaction.signatures[0];
734
735 let loaded_addresses = self
738 .get_loaded_addresses(remote_ctx, &transaction.message)
739 .await?;
740 let pubkeys_from_message = self
741 .get_pubkeys_from_message(&transaction.message, loaded_addresses.clone())
742 .clone();
743
744 let account_updates = self
745 .get_multiple_accounts(remote_ctx, &pubkeys_from_message, None)
746 .await?
747 .inner;
748
749 let pre_execution_capture = {
750 let mut capture = BTreeMap::new();
751 for account_update in account_updates.iter() {
752 self.snapshot_get_account_result(
753 &mut capture,
754 account_update.clone(),
755 Some(UiAccountEncoding::JsonParsed),
756 );
757 }
758 capture
759 };
760
761 self.with_svm_writer(|svm_writer| {
762 for update in &account_updates {
763 svm_writer.write_account_update(update.clone());
764 }
765
766 let accounts_before = pubkeys_from_message
767 .iter()
768 .map(|p| svm_writer.inner.get_account(p))
769 .collect::<Vec<Option<Account>>>();
770
771 let token_accounts_before = pubkeys_from_message
772 .iter()
773 .enumerate()
774 .filter_map(|(i, p)| {
775 svm_writer
776 .token_accounts
777 .get(&p)
778 .cloned()
779 .map(|a| (i, a))
780 .clone()
781 })
782 .collect::<Vec<_>>();
783
784 let token_programs = token_accounts_before
785 .iter()
786 .map(|(i, _)| {
787 svm_writer
788 .accounts_registry
789 .get(&pubkeys_from_message[*i])
790 .unwrap()
791 .owner
792 })
793 .collect::<Vec<_>>()
794 .clone();
795 let mut logs = vec![];
796
797 if !skip_preflight {
799 match svm_writer.simulate_transaction(transaction.clone(), true) {
800 Ok(_) => {}
801 Err(res) => {
802 let _ = svm_writer
803 .simnet_events_tx
804 .try_send(SimnetEvent::error(format!(
805 "Transaction simulation failed: {}",
806 res.err
807 )));
808 let meta = convert_transaction_metadata_from_canonical(&res.meta);
809 logs = meta.logs.clone();
810 let _ = status_tx.try_send(TransactionStatusEvent::SimulationFailure((
811 res.err.clone(),
812 meta,
813 )));
814 svm_writer.notify_signature_subscribers(
815 SignatureSubscriptionType::processed(),
816 &signature,
817 latest_absolute_slot,
818 Some(res.err.clone()),
819 );
820 svm_writer.notify_logs_subscribers(
821 &signature,
822 Some(res.err),
823 logs,
824 CommitmentLevel::Processed,
825 );
826 return Ok::<(), SurfpoolError>(());
827 }
828 }
829 }
830 let err = match svm_writer
832 .send_transaction(transaction.clone(), false )
833 {
834 Ok(res) => {
835 logs = res.logs.clone();
836 let accounts_after = pubkeys_from_message
837 .iter()
838 .map(|p| svm_writer.inner.get_account(p))
839 .collect::<Vec<Option<Account>>>();
840
841 let sanitized_transaction = SanitizedTransaction::try_create(
842 transaction.clone(),
843 transaction.message.hash(),
844 Some(false),
845 if let Some(loaded_addresses) = &loaded_addresses {
846 SimpleAddressLoader::Enabled(loaded_addresses.clone())
847 } else {
848 SimpleAddressLoader::Disabled
849 },
850 &HashSet::new(), )
852 .ok();
853
854 for (pubkey, (before, after)) in pubkeys_from_message
855 .iter()
856 .zip(accounts_before.clone().iter().zip(accounts_after.clone()))
857 {
858 if before.ne(&after) {
859 if let Some(after) = &after {
860 svm_writer.update_account_registries(pubkey, after);
861 let write_version = svm_writer.increment_write_version();
862
863 if let Some(sanitized_transaction) = sanitized_transaction.clone() {
864 let _ = svm_writer.geyser_events_tx.send(
865 GeyserEvent::UpdateAccount(GeyserAccountUpdate::new(
866 *pubkey,
867 after.clone(),
868 svm_writer.get_latest_absolute_slot(),
869 sanitized_transaction.clone(),
870 write_version,
871 )),
872 );
873 }
874 }
875 svm_writer
876 .notify_account_subscribers(pubkey, &after.unwrap_or_default());
877 }
878 }
879
880 let mut token_accounts_after = vec![];
881 let mut post_execution_capture = BTreeMap::new();
882 for (i, (pubkey, account)) in pubkeys_from_message
883 .iter()
884 .zip(accounts_after.iter())
885 .enumerate()
886 {
887 let token_account = svm_writer.token_accounts.get(&pubkey).cloned();
888
889 let ui_account = if let Some(account) = account {
890 let ui_account = svm_writer
891 .account_to_rpc_keyed_account(
892 &pubkey,
893 account,
894 &RpcAccountInfoConfig {
895 encoding: Some(UiAccountEncoding::JsonParsed),
896 ..Default::default()
897 },
898 token_account.map(|a| a.mint()),
899 )
900 .account;
901 Some(ui_account)
902 } else {
903 None
904 };
905 post_execution_capture.insert(*pubkey, ui_account);
906
907 if let Some(token_account) = token_account {
908 token_accounts_after.push((i, token_account));
909 }
910 }
911
912 let token_mints = token_accounts_after
913 .iter()
914 .map(|(_, a)| {
915 svm_writer
916 .token_mints
917 .get(&a.mint())
918 .ok_or(SurfpoolError::token_mint_not_found(a.mint()))
919 .cloned()
920 })
921 .collect::<Result<Vec<_>, SurfpoolError>>()?;
922
923 let profile_result = ProfileResult::success(
924 res.compute_units_consumed,
925 res.logs.clone(),
926 pre_execution_capture.clone(),
927 post_execution_capture,
928 latest_absolute_slot,
929 Some(Uuid::new_v4()),
930 );
931 svm_writer.write_executed_profile_result(signature, profile_result);
932
933 let transaction_meta = convert_transaction_metadata_from_canonical(&res);
934
935 let transaction_with_status_meta = TransactionWithStatusMeta::new(
936 svm_writer.get_latest_absolute_slot(),
937 transaction.clone(),
938 res,
939 accounts_before,
940 accounts_after,
941 token_accounts_before,
942 token_accounts_after,
943 token_mints,
944 token_programs,
945 loaded_addresses.clone().unwrap_or_default(),
946 );
947 svm_writer.transactions.insert(
948 transaction_meta.signature,
949 SurfnetTransactionStatus::Processed(Box::new(
950 transaction_with_status_meta.clone(),
951 )),
952 );
953
954 let _ = svm_writer
955 .simnet_events_tx
956 .try_send(SimnetEvent::transaction_processed(transaction_meta, None));
957
958 let _ = svm_writer
959 .geyser_events_tx
960 .send(GeyserEvent::NotifyTransaction(transaction_with_status_meta));
961
962 let _ = status_tx.try_send(TransactionStatusEvent::Success(
963 TransactionConfirmationStatus::Processed,
964 ));
965 svm_writer
966 .transactions_queued_for_confirmation
967 .push_back((transaction.clone(), status_tx.clone()));
968 None
969 }
970 Err(res) => {
971 let transaction_meta = convert_transaction_metadata_from_canonical(&res.meta);
972 let _ = svm_writer
973 .simnet_events_tx
974 .try_send(SimnetEvent::error(format!(
975 "Transaction execution failed: {}",
976 res.err
977 )));
978 let _ = status_tx.try_send(TransactionStatusEvent::ExecutionFailure((
979 res.err.clone(),
980 transaction_meta,
981 )));
982 Some(res.err)
983 }
984 };
985
986 svm_writer.notify_signature_subscribers(
987 SignatureSubscriptionType::processed(),
988 &signature,
989 latest_absolute_slot,
990 err.clone(),
991 );
992 svm_writer.notify_logs_subscribers(&signature, err, logs, CommitmentLevel::Processed);
993 Ok(())
994 })?;
995
996 Ok(SvmAccessContext::new(
997 latest_absolute_slot,
998 latest_epoch_info,
999 latest_blockhash,
1000 (),
1001 ))
1002 }
1003}
1004
1005impl SurfnetSvmLocker {
1007 pub fn write_account_update(&self, account_update: GetAccountResult) {
1009 if !account_update.requires_update() {
1010 return;
1011 }
1012
1013 self.with_svm_writer(move |svm_writer| {
1014 svm_writer.write_account_update(account_update.clone())
1015 })
1016 }
1017
1018 pub fn write_multiple_account_updates(&self, account_updates: &[GetAccountResult]) {
1020 if account_updates
1021 .iter()
1022 .all(|update| !update.requires_update())
1023 {
1024 return;
1025 }
1026
1027 self.with_svm_writer(move |svm_writer| {
1028 for update in account_updates {
1029 svm_writer.write_account_update(update.clone());
1030 }
1031 });
1032 }
1033}
1034
1035impl SurfnetSvmLocker {
1037 pub async fn get_token_accounts_by_owner(
1039 &self,
1040 remote_ctx: &Option<SurfnetRemoteClient>,
1041 owner: Pubkey,
1042 filter: &TokenAccountsFilter,
1043 config: &RpcAccountInfoConfig,
1044 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1045 if let Some(remote_client) = remote_ctx {
1046 self.get_token_accounts_by_owner_local_then_remote(owner, filter, remote_client, config)
1047 .await
1048 } else {
1049 Ok(self.get_token_accounts_by_owner_local(owner, filter, config))
1050 }
1051 }
1052
1053 pub fn get_token_accounts_by_owner_local(
1054 &self,
1055 owner: Pubkey,
1056 filter: &TokenAccountsFilter,
1057 config: &RpcAccountInfoConfig,
1058 ) -> SvmAccessContext<Vec<RpcKeyedAccount>> {
1059 self.with_contextualized_svm_reader(|svm_reader| {
1060 svm_reader
1061 .get_parsed_token_accounts_by_owner(&owner)
1062 .iter()
1063 .filter_map(|(pubkey, token_account)| {
1064 let account = svm_reader.accounts_registry.get(pubkey)?;
1065 if match filter {
1066 TokenAccountsFilter::Mint(mint) => token_account.mint().eq(mint),
1067 TokenAccountsFilter::ProgramId(program_id) => account.owner.eq(program_id),
1068 } {
1069 Some(svm_reader.account_to_rpc_keyed_account(
1070 pubkey,
1071 account,
1072 config,
1073 Some(token_account.mint()),
1074 ))
1075 } else {
1076 None
1077 }
1078 })
1079 .collect::<Vec<_>>()
1080 })
1081 }
1082
1083 pub async fn get_token_accounts_by_owner_local_then_remote(
1084 &self,
1085 owner: Pubkey,
1086 filter: &TokenAccountsFilter,
1087 remote_client: &SurfnetRemoteClient,
1088 config: &RpcAccountInfoConfig,
1089 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1090 let SvmAccessContext {
1091 slot,
1092 latest_epoch_info,
1093 latest_blockhash,
1094 inner: local_accounts,
1095 } = self.get_token_accounts_by_owner_local(owner, filter, config);
1096
1097 let remote_accounts = remote_client
1098 .get_token_accounts_by_owner(owner, filter, config)
1099 .await?;
1100
1101 let mut combined_accounts = remote_accounts;
1102
1103 for local_account in local_accounts {
1104 if let Some((pos, _)) = combined_accounts
1105 .iter()
1106 .find_position(|RpcKeyedAccount { pubkey, .. }| pubkey.eq(&local_account.pubkey))
1107 {
1108 combined_accounts[pos] = local_account;
1109 } else {
1110 combined_accounts.push(local_account);
1111 }
1112 }
1113
1114 Ok(SvmAccessContext::new(
1115 slot,
1116 latest_epoch_info,
1117 latest_blockhash,
1118 combined_accounts,
1119 ))
1120 }
1121
1122 pub async fn get_token_accounts_by_delegate(
1123 &self,
1124 remote_ctx: &Option<SurfnetRemoteClient>,
1125 delegate: Pubkey,
1126 filter: &TokenAccountsFilter,
1127 config: &RpcAccountInfoConfig,
1128 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1129 if let TokenAccountsFilter::ProgramId(program_id) = filter {
1131 if !is_supported_token_program(program_id) {
1132 return Err(SurfpoolError::unsupported_token_program(*program_id));
1133 }
1134 }
1135
1136 if let Some(remote_client) = remote_ctx {
1137 self.get_token_accounts_by_delegate_local_then_remote(
1138 delegate,
1139 filter,
1140 remote_client,
1141 config,
1142 )
1143 .await
1144 } else {
1145 Ok(self.get_token_accounts_by_delegate_local(delegate, filter, config))
1146 }
1147 }
1148}
1149
1150impl SurfnetSvmLocker {
1152 pub fn get_token_accounts_by_delegate_local(
1153 &self,
1154 delegate: Pubkey,
1155 filter: &TokenAccountsFilter,
1156 config: &RpcAccountInfoConfig,
1157 ) -> SvmAccessContext<Vec<RpcKeyedAccount>> {
1158 self.with_contextualized_svm_reader(|svm_reader| {
1159 svm_reader
1160 .get_token_accounts_by_delegate(&delegate)
1161 .iter()
1162 .filter_map(|(pubkey, token_account)| {
1163 let account = svm_reader.accounts_registry.get(pubkey)?;
1164 let include = match filter {
1165 TokenAccountsFilter::Mint(mint) => token_account.mint() == *mint,
1166 TokenAccountsFilter::ProgramId(program_id) => {
1167 account.owner == *program_id && is_supported_token_program(program_id)
1168 }
1169 };
1170
1171 if include {
1172 Some(svm_reader.account_to_rpc_keyed_account(
1173 pubkey,
1174 account,
1175 config,
1176 Some(token_account.mint()),
1177 ))
1178 } else {
1179 None
1180 }
1181 })
1182 .collect::<Vec<_>>()
1183 })
1184 }
1185
1186 pub async fn get_token_accounts_by_delegate_local_then_remote(
1187 &self,
1188 delegate: Pubkey,
1189 filter: &TokenAccountsFilter,
1190 remote_client: &SurfnetRemoteClient,
1191 config: &RpcAccountInfoConfig,
1192 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1193 let SvmAccessContext {
1194 slot,
1195 latest_epoch_info,
1196 latest_blockhash,
1197 inner: local_accounts,
1198 } = self.get_token_accounts_by_delegate_local(delegate, filter, config);
1199
1200 let remote_accounts = remote_client
1201 .get_token_accounts_by_delegate(delegate, filter, config)
1202 .await?;
1203
1204 let mut combined_accounts = remote_accounts;
1205
1206 for local_account in local_accounts {
1207 if let Some((pos, _)) = combined_accounts
1208 .iter()
1209 .find_position(|RpcKeyedAccount { pubkey, .. }| pubkey.eq(&local_account.pubkey))
1210 {
1211 combined_accounts[pos] = local_account;
1213 } else {
1214 combined_accounts.push(local_account);
1216 }
1217 }
1218
1219 Ok(SvmAccessContext::new(
1220 slot,
1221 latest_epoch_info,
1222 latest_blockhash,
1223 combined_accounts,
1224 ))
1225 }
1226}
1227
1228impl SurfnetSvmLocker {
1230 pub fn get_token_largest_accounts_local(
1231 &self,
1232 mint: &Pubkey,
1233 ) -> SvmAccessContext<Vec<RpcTokenAccountBalance>> {
1234 self.with_contextualized_svm_reader(|svm_reader| {
1235 let token_accounts = svm_reader.get_token_accounts_by_mint(mint);
1236
1237 let mint_decimals = if let Some(mint_account) = svm_reader.token_mints.get(mint) {
1239 mint_account.decimals()
1240 } else {
1241 0
1242 };
1243
1244 let mut balances: Vec<RpcTokenAccountBalance> = token_accounts
1246 .into_iter()
1247 .map(|(pubkey, token_account)| RpcTokenAccountBalance {
1248 address: pubkey.to_string(),
1249 amount: UiTokenAmount {
1250 amount: token_account.amount().to_string(),
1251 decimals: mint_decimals,
1252 ui_amount: Some(format_ui_amount(token_account.amount(), mint_decimals)),
1253 ui_amount_string: format_ui_amount_string(
1254 token_account.amount(),
1255 mint_decimals,
1256 ),
1257 },
1258 })
1259 .collect();
1260
1261 balances.sort_by(|a, b| {
1263 let amount_a: u64 = a.amount.amount.parse().unwrap_or(0);
1264 let amount_b: u64 = b.amount.amount.parse().unwrap_or(0);
1265 amount_b.cmp(&amount_a)
1266 });
1267
1268 balances.truncate(20);
1270
1271 balances
1272 })
1273 }
1274
1275 pub async fn get_token_largest_accounts_local_then_remote(
1276 &self,
1277 client: &SurfnetRemoteClient,
1278 mint: &Pubkey,
1279 commitment_config: CommitmentConfig,
1280 ) -> SurfpoolContextualizedResult<Vec<RpcTokenAccountBalance>> {
1281 let SvmAccessContext {
1282 slot,
1283 latest_epoch_info,
1284 latest_blockhash,
1285 inner: local_accounts,
1286 } = self.get_token_largest_accounts_local(mint);
1287
1288 let remote_accounts = client
1289 .get_token_largest_accounts(mint, commitment_config)
1290 .await?;
1291
1292 let mut combined_accounts = remote_accounts;
1293
1294 for local_account in local_accounts {
1296 if let Some((pos, _)) = combined_accounts
1297 .iter()
1298 .find_position(|remote_account| remote_account.address == local_account.address)
1299 {
1300 combined_accounts[pos] = local_account;
1301 } else {
1302 combined_accounts.push(local_account);
1303 }
1304 }
1305
1306 combined_accounts.sort_by(|a, b| {
1308 let amount_a: u64 = a.amount.amount.parse().unwrap_or(0);
1309 let amount_b: u64 = b.amount.amount.parse().unwrap_or(0);
1310 amount_b.cmp(&amount_a)
1311 });
1312 combined_accounts.truncate(20);
1313
1314 Ok(SvmAccessContext::new(
1315 slot,
1316 latest_epoch_info,
1317 latest_blockhash,
1318 combined_accounts,
1319 ))
1320 }
1321
1322 pub async fn get_token_largest_accounts(
1324 &self,
1325 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1326 mint: &Pubkey,
1327 ) -> SurfpoolContextualizedResult<Vec<RpcTokenAccountBalance>> {
1328 if let Some((remote_client, commitment_config)) = remote_ctx {
1329 self.get_token_largest_accounts_local_then_remote(
1330 remote_client,
1331 mint,
1332 *commitment_config,
1333 )
1334 .await
1335 } else {
1336 Ok(self.get_token_largest_accounts_local(mint))
1337 }
1338 }
1339}
1340
1341impl SurfnetSvmLocker {
1343 pub fn get_pubkeys_from_message(
1345 &self,
1346 message: &VersionedMessage,
1347 loaded_addresses: Option<LoadedAddresses>,
1348 ) -> Vec<Pubkey> {
1349 match message {
1350 VersionedMessage::Legacy(message) => message.account_keys.clone(),
1351 VersionedMessage::V0(message) => {
1352 let alts = message.address_table_lookups.clone();
1353 let mut acc_keys = message.account_keys.clone();
1354 let mut alt_pubkeys = alts.iter().map(|msg| msg.account_key).collect::<Vec<_>>();
1355
1356 acc_keys.append(&mut alt_pubkeys);
1357 if let Some(mut loaded_addresses) = loaded_addresses {
1358 acc_keys.append(&mut loaded_addresses.readonly);
1359 acc_keys.append(&mut loaded_addresses.writable);
1360 }
1361 acc_keys
1362 }
1363 }
1364 }
1365
1366 pub async fn get_loaded_addresses(
1368 &self,
1369 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1370 message: &VersionedMessage,
1371 ) -> SurfpoolResult<Option<LoadedAddresses>> {
1372 match message {
1373 VersionedMessage::Legacy(_) => Ok(None),
1374 VersionedMessage::V0(message) => {
1375 let alts = message.address_table_lookups.clone();
1376 if alts.is_empty() {
1377 return Ok(None);
1378 }
1379 let mut combined = LoadedAddresses::default();
1380 for alt in alts {
1381 let mut loaded_addresses = self
1382 .get_lookup_table_addresses(remote_ctx, &alt)
1383 .await?
1384 .inner;
1385 combined.readonly.append(&mut loaded_addresses.readonly);
1386 combined.writable.append(&mut loaded_addresses.writable);
1387 }
1388
1389 Ok(Some(combined))
1390 }
1391 }
1392 }
1393
1394 pub async fn get_lookup_table_addresses(
1396 &self,
1397 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1398 address_table_lookup: &MessageAddressTableLookup,
1399 ) -> SurfpoolContextualizedResult<LoadedAddresses> {
1400 let result = self
1401 .get_account(remote_ctx, &address_table_lookup.account_key, None)
1402 .await?;
1403 let table_account = result.inner.clone().map_account()?;
1404
1405 if table_account.owner == solana_sdk_ids::address_lookup_table::id() {
1406 let SvmAccessContext {
1407 slot: current_slot,
1408 inner: slot_hashes,
1409 ..
1410 } = self.with_contextualized_svm_reader(|svm_reader| {
1411 svm_reader
1412 .inner
1413 .get_sysvar::<solana_sdk::sysvar::slot_hashes::SlotHashes>()
1414 });
1415
1416 let data = &table_account.data.clone();
1418 let lookup_table = AddressLookupTable::deserialize(data).map_err(|_ix_err| {
1419 SurfpoolError::invalid_account_data(
1420 address_table_lookup.account_key,
1421 table_account.data,
1422 Some("Attempted to lookup addresses from an invalid account"),
1423 )
1424 })?;
1425
1426 let loaded_addresses = LoadedAddresses {
1427 writable: lookup_table
1428 .lookup(
1429 current_slot,
1430 &address_table_lookup.writable_indexes,
1431 &slot_hashes,
1432 )
1433 .map_err(|_ix_err| {
1434 SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
1435 })?,
1436 readonly: lookup_table
1437 .lookup(
1438 current_slot,
1439 &address_table_lookup.readonly_indexes,
1440 &slot_hashes,
1441 )
1442 .map_err(|_ix_err| {
1443 SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
1444 })?,
1445 };
1446 Ok(result.with_new_value(loaded_addresses))
1447 } else {
1448 Err(SurfpoolError::invalid_account_owner(
1449 table_account.owner,
1450 Some("Attempted to lookup addresses from an account owned by the wrong program"),
1451 ))
1452 }
1453 }
1454}
1455
1456impl SurfnetSvmLocker {
1458 pub fn estimate_compute_units(
1460 &self,
1461 transaction: &VersionedTransaction,
1462 ) -> SvmAccessContext<ComputeUnitsEstimationResult> {
1463 self.with_contextualized_svm_reader(|svm_reader| {
1464 svm_reader.estimate_compute_units(transaction)
1465 })
1466 }
1467 pub async fn profile_transaction(
1468 &self,
1469 remote_ctx: &Option<SurfnetRemoteClient>,
1470 transaction: VersionedTransaction,
1471 encoding: Option<UiAccountEncoding>,
1472 tag: Option<String>,
1473 ) -> SurfpoolContextualizedResult<ProfileResult> {
1474 let SvmAccessContext {
1475 slot,
1476 latest_epoch_info,
1477 latest_blockhash,
1478 inner: mut svm_clone,
1479 } = self.with_contextualized_svm_reader(|svm_reader| svm_reader.clone());
1480
1481 let (dummy_simnet_tx, _) = crossbeam_channel::bounded(1);
1482 let (dummy_geyser_tx, _) = crossbeam_channel::bounded(1);
1483 svm_clone.simnet_events_tx = dummy_simnet_tx;
1484 svm_clone.geyser_events_tx = dummy_geyser_tx;
1485
1486 let svm_locker = SurfnetSvmLocker::new(svm_clone);
1487
1488 let remote_ctx_with_config = remote_ctx
1489 .clone()
1490 .map(|client| (client, CommitmentConfig::confirmed()));
1491
1492 let loaded_addresses = svm_locker
1493 .get_loaded_addresses(&remote_ctx_with_config, &transaction.message)
1494 .await?;
1495 let account_keys =
1496 svm_locker.get_pubkeys_from_message(&transaction.message, loaded_addresses);
1497
1498 let pre_execution_capture = {
1499 let mut capture = BTreeMap::new();
1500 for pubkey in &account_keys {
1501 let account = svm_locker
1502 .get_account(&remote_ctx_with_config, pubkey, None)
1503 .await?
1504 .inner;
1505
1506 svm_locker.snapshot_get_account_result(&mut capture, account, encoding);
1507 }
1508 capture
1509 };
1510
1511 let compute_units_estimation_result = svm_locker.estimate_compute_units(&transaction).inner;
1512
1513 let (status_tx, status_rx) = crossbeam_channel::unbounded();
1514 let signature = transaction.signatures[0];
1515 let _ = svm_locker
1516 .process_transaction(remote_ctx, transaction, status_tx, true)
1517 .await?;
1518
1519 let simnet_events_tx = self.simnet_events_tx();
1520 loop {
1521 if let Ok(status) = status_rx.try_recv() {
1522 match status {
1523 TransactionStatusEvent::Success(_) => break,
1524 TransactionStatusEvent::ExecutionFailure((err, _)) => {
1525 let _ = simnet_events_tx.try_send(SimnetEvent::WarnLog(
1526 chrono::Local::now(),
1527 format!(
1528 "Transaction {} failed during snapshot simulation: {}",
1529 signature, err
1530 ),
1531 ));
1532 return Err(SurfpoolError::internal(format!(
1533 "Transaction {} failed during snapshot simulation: {}",
1534 signature, err
1535 )));
1536 }
1537 TransactionStatusEvent::SimulationFailure(_) => unreachable!(),
1538 TransactionStatusEvent::VerificationFailure(_) => {
1539 let _ = simnet_events_tx.try_send(SimnetEvent::WarnLog(
1540 chrono::Local::now(),
1541 format!(
1542 "Transaction {} verification failed during snapshot simulation",
1543 signature
1544 ),
1545 ));
1546 return Err(SurfpoolError::internal(format!(
1547 "Transaction {} verification failed during snapshot simulation",
1548 signature
1549 )));
1550 }
1551 }
1552 }
1553 }
1554
1555 let post_execution_capture = {
1556 let mut capture = BTreeMap::new();
1557 for pubkey in &account_keys {
1558 let account = svm_locker.get_account_local(pubkey).inner;
1561
1562 svm_locker.snapshot_get_account_result(&mut capture, account, encoding);
1563 }
1564 capture
1565 };
1566
1567 let uuid = Uuid::new_v4();
1568 let profile_result = ProfileResult {
1569 compute_units: compute_units_estimation_result,
1570 state: ProfileState::new(pre_execution_capture, post_execution_capture),
1571 slot,
1572 uuid: Some(uuid),
1573 };
1574
1575 self.with_svm_writer(|svm| {
1576 svm.simulated_transaction_profiles
1577 .insert(uuid, profile_result.clone());
1578 svm.profile_tag_map
1579 .entry(tag.clone().unwrap_or(uuid.to_string()))
1580 .or_default()
1581 .push(UuidOrSignature::Uuid(uuid));
1582 });
1583
1584 Ok(SvmAccessContext::new(
1585 slot,
1586 latest_epoch_info,
1587 latest_blockhash,
1588 profile_result,
1589 ))
1590 }
1591
1592 pub fn get_profile_result(
1594 &self,
1595 signature_or_uuid: UuidOrSignature,
1596 ) -> SurfpoolResult<Option<ProfileResult>> {
1597 match &signature_or_uuid {
1598 UuidOrSignature::Signature(signature) => {
1599 let profile = self.with_svm_reader(|svm| {
1600 svm.executed_transaction_profiles.get(signature).cloned()
1601 });
1602 let transaction_exists =
1603 self.with_svm_reader(|svm| svm.transactions.contains_key(signature));
1604 if profile.is_none() && transaction_exists {
1605 Err(SurfpoolError::transaction_not_found_in_svm(signature))
1606 } else {
1607 Ok(profile)
1608 }
1609 }
1610 UuidOrSignature::Uuid(uuid) => {
1611 let profile = self
1612 .with_svm_reader(|svm| svm.simulated_transaction_profiles.get(uuid).cloned());
1613 Ok(profile)
1614 }
1615 }
1616 }
1617
1618 pub fn get_profile_results_by_tag(
1620 &self,
1621 tag: String,
1622 ) -> SurfpoolResult<Option<Vec<ProfileResult>>> {
1623 let tag_map = self.with_svm_reader(|svm| svm.profile_tag_map.get(&tag).cloned());
1624 match tag_map {
1625 None => Ok(None),
1626 Some(uuids_or_sigs) => {
1627 let mut profiles = Vec::new();
1628 for id in uuids_or_sigs {
1629 let profile = self.get_profile_result(id.clone())?;
1630 if profile.is_none() {
1631 return Err(SurfpoolError::tag_not_found(&tag));
1632 }
1633 profiles.push(profile.unwrap());
1634 }
1635 Ok(Some(profiles))
1636 }
1637 }
1638 }
1639}
1640impl SurfnetSvmLocker {
1642 pub async fn clone_program_account(
1644 &self,
1645 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1646 source_program_id: &Pubkey,
1647 destination_program_id: &Pubkey,
1648 ) -> SurfpoolContextualizedResult<()> {
1649 let expected_source_program_data_address = get_program_data_address(source_program_id);
1650
1651 let result = self
1652 .get_multiple_accounts(
1653 remote_ctx,
1654 &[*source_program_id, expected_source_program_data_address],
1655 None,
1656 )
1657 .await?;
1658
1659 let mut accounts = result
1660 .inner
1661 .clone()
1662 .into_iter()
1663 .map(|a| a.map_account())
1664 .collect::<SurfpoolResult<Vec<Account>>>()?;
1665
1666 let source_program_data_account = accounts.remove(1);
1667 let source_program_account = accounts.remove(0);
1668
1669 let BpfUpgradeableLoaderAccountType::Program(UiProgram {
1670 program_data: source_program_data_address,
1671 }) = parse_bpf_upgradeable_loader(&source_program_account.data).map_err(|e| {
1672 SurfpoolError::invalid_program_account(source_program_id, e.to_string())
1673 })?
1674 else {
1675 return Err(SurfpoolError::expected_program_account(source_program_id));
1676 };
1677
1678 if source_program_data_address.ne(&expected_source_program_data_address.to_string()) {
1679 return Err(SurfpoolError::invalid_program_account(
1680 source_program_id,
1681 format!(
1682 "Program data address mismatch: expected {}, found {}",
1683 expected_source_program_data_address, source_program_data_address
1684 ),
1685 ));
1686 }
1687
1688 let destination_program_data_address = get_program_data_address(destination_program_id);
1689
1690 let mut new_program_account = source_program_account;
1693 new_program_account.data = bincode::serialize(&UpgradeableLoaderState::Program {
1694 programdata_address: destination_program_data_address,
1695 })
1696 .map_err(|e| SurfpoolError::internal(format!("Failed to serialize program data: {}", e)))?;
1697
1698 self.with_svm_writer(|svm_writer| {
1699 svm_writer.set_account(
1700 &destination_program_data_address,
1701 source_program_data_account.clone(),
1702 )?;
1703
1704 svm_writer.set_account(destination_program_id, new_program_account.clone())?;
1705 Ok::<(), SurfpoolError>(())
1706 })?;
1707
1708 Ok(result.with_new_value(()))
1709 }
1710
1711 pub async fn set_program_authority(
1712 &self,
1713 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1714 program_id: Pubkey,
1715 new_authority: Option<Pubkey>,
1716 ) -> SurfpoolContextualizedResult<()> {
1717 let SvmAccessContext {
1718 slot,
1719 latest_epoch_info,
1720 latest_blockhash,
1721 inner: mut get_account_result,
1722 } = self.get_account(remote_ctx, &program_id, None).await?;
1723
1724 let original_authority = match &mut get_account_result {
1725 GetAccountResult::None(pubkey) => {
1726 return Err(SurfpoolError::invalid_program_account(
1727 pubkey,
1728 "Account not found",
1729 ));
1730 }
1731 GetAccountResult::FoundAccount(pubkey, program_account, _) => {
1732 let programdata_address = get_program_data_address(pubkey);
1733 let mut programdata_account_result = self
1734 .get_account(remote_ctx, &programdata_address, None)
1735 .await?
1736 .inner;
1737 match &mut programdata_account_result {
1738 GetAccountResult::None(pubkey) => {
1739 return Err(SurfpoolError::invalid_program_account(
1740 pubkey,
1741 "Program data account does not exist",
1742 ));
1743 }
1744 GetAccountResult::FoundAccount(_, programdata_account, _) => {
1745 let original_authority = update_programdata_account(
1746 &program_id,
1747 programdata_account,
1748 new_authority,
1749 )?;
1750
1751 get_account_result = GetAccountResult::FoundProgramAccount(
1752 (pubkey.clone(), program_account.clone()),
1753 (programdata_address, Some(programdata_account.clone())),
1754 );
1755
1756 original_authority
1757 }
1758 GetAccountResult::FoundProgramAccount(_, _)
1759 | GetAccountResult::FoundTokenAccount(_, _) => {
1760 return Err(SurfpoolError::invalid_program_account(
1761 pubkey,
1762 "Not a program account",
1763 ));
1764 }
1765 }
1766 }
1767 GetAccountResult::FoundProgramAccount(_, (_, None)) => {
1768 return Err(SurfpoolError::invalid_program_account(
1769 &program_id,
1770 "Program data account does not exist",
1771 ));
1772 }
1773 GetAccountResult::FoundProgramAccount(_, (_, Some(programdata_account))) => {
1774 update_programdata_account(&program_id, programdata_account, new_authority)?
1775 }
1776 GetAccountResult::FoundTokenAccount(_, _) => {
1777 return Err(SurfpoolError::invalid_program_account(
1778 program_id,
1779 "Not a program account",
1780 ));
1781 }
1782 };
1783
1784 let simnet_events_tx = self.simnet_events_tx();
1785 match (original_authority, new_authority) {
1786 (Some(original), Some(new)) => {
1787 if original != new {
1788 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1789 "Setting new authority for program {}",
1790 program_id
1791 )));
1792 let _ = simnet_events_tx
1793 .send(SimnetEvent::info(format!("Old Authority: {}", original)));
1794 let _ =
1795 simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
1796 } else {
1797 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1798 "No authority change for program {}",
1799 program_id
1800 )));
1801 }
1802 }
1803 (Some(original), None) => {
1804 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1805 "Removing authority for program {}",
1806 program_id
1807 )));
1808 let _ = simnet_events_tx
1809 .send(SimnetEvent::info(format!("Old Authority: {}", original)));
1810 }
1811 (None, Some(new)) => {
1812 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1813 "Setting new authority for program {}",
1814 program_id
1815 )));
1816 let _ = simnet_events_tx.send(SimnetEvent::info(format!("Old Authority: None")));
1817 let _ = simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
1818 }
1819 (None, None) => {
1820 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1821 "No authority change for program {}",
1822 program_id
1823 )));
1824 }
1825 };
1826
1827 self.write_account_update(get_account_result);
1828
1829 Ok(SvmAccessContext::new(
1830 slot,
1831 latest_epoch_info,
1832 latest_blockhash,
1833 (),
1834 ))
1835 }
1836
1837 pub async fn get_program_accounts(
1838 &self,
1839 remote_ctx: &Option<SurfnetRemoteClient>,
1840 program_id: &Pubkey,
1841 account_config: RpcAccountInfoConfig,
1842 filters: Option<Vec<RpcFilterType>>,
1843 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1844 if let Some(remote_client) = remote_ctx {
1845 self.get_program_accounts_local_then_remote(
1846 remote_client,
1847 program_id,
1848 account_config,
1849 filters,
1850 )
1851 .await
1852 } else {
1853 self.get_program_accounts_local(program_id, account_config, filters)
1854 }
1855 }
1856
1857 pub fn get_program_accounts_local(
1859 &self,
1860 program_id: &Pubkey,
1861 account_config: RpcAccountInfoConfig,
1862 filters: Option<Vec<RpcFilterType>>,
1863 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1864 let res = self.with_svm_reader(|svm_reader| {
1865 let res = svm_reader.get_account_owned_by(*program_id);
1866
1867 let mut filtered = vec![];
1868 for (pubkey, account) in &res {
1869 if let Some(ref active_filters) = filters {
1870 match apply_rpc_filters(&account.data, active_filters) {
1871 Ok(true) => {} Ok(false) => continue, Err(e) => return Err(e), }
1875 }
1876
1877 filtered.push(svm_reader.account_to_rpc_keyed_account(
1878 pubkey,
1879 account,
1880 &account_config,
1881 None,
1882 ));
1883 }
1884 Ok(filtered)
1885 })?;
1886
1887 Ok(self.with_contextualized_svm_reader(|_| res.clone()))
1888 }
1889
1890 pub async fn get_program_accounts_local_then_remote(
1892 &self,
1893 client: &SurfnetRemoteClient,
1894 program_id: &Pubkey,
1895 account_config: RpcAccountInfoConfig,
1896 filters: Option<Vec<RpcFilterType>>,
1897 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1898 let SvmAccessContext {
1899 slot,
1900 latest_epoch_info,
1901 latest_blockhash,
1902 inner: local_accounts,
1903 } = self.get_program_accounts_local(program_id, account_config.clone(), filters.clone())?;
1904 let remote_accounts = client
1905 .get_program_accounts(program_id, account_config, filters)
1906 .await?;
1907
1908 let mut combined_accounts = remote_accounts;
1909
1910 for local_account in local_accounts {
1911 if let Some((pos, _)) = combined_accounts.iter().find_position(
1913 |RpcKeyedAccount {
1914 pubkey: remote_pubkey,
1915 ..
1916 }| remote_pubkey.eq(&local_account.pubkey),
1917 ) {
1918 combined_accounts[pos] = local_account;
1919 } else {
1920 combined_accounts.push(local_account);
1922 };
1923 }
1924
1925 Ok(SvmAccessContext {
1926 slot,
1927 latest_epoch_info,
1928 latest_blockhash,
1929 inner: combined_accounts,
1930 })
1931 }
1932}
1933
1934impl SurfnetSvmLocker {
1935 pub fn get_first_local_slot(&self) -> Option<Slot> {
1936 self.with_svm_reader(|svm_reader| svm_reader.blocks.keys().min().copied())
1937 }
1938
1939 pub async fn get_block(
1940 &self,
1941 remote_ctx: &Option<SurfnetRemoteClient>,
1942 slot: &Slot,
1943 config: &RpcBlockConfig,
1944 ) -> SurfpoolContextualizedResult<Option<UiConfirmedBlock>> {
1945 let first_local_slot = self.get_first_local_slot();
1946
1947 let result = if first_local_slot.is_some() && first_local_slot.unwrap() > *slot {
1948 match remote_ctx {
1949 Some(remote_client) => Some(remote_client.get_block(slot, *config).await?),
1950 None => return Err(SurfpoolError::slot_too_old(*slot)),
1951 }
1952 } else {
1953 self.get_block_local(slot, config)?
1954 };
1955
1956 Ok(SvmAccessContext {
1957 slot: *slot,
1958 latest_epoch_info: self.get_epoch_info(),
1959 latest_blockhash: self
1960 .get_latest_blockhash(&CommitmentConfig::processed())
1961 .unwrap_or_default(),
1962 inner: result,
1963 })
1964 }
1965
1966 pub fn get_block_local(
1967 &self,
1968 slot: &Slot,
1969 config: &RpcBlockConfig,
1970 ) -> SurfpoolResult<Option<UiConfirmedBlock>> {
1971 self.with_svm_reader(|svm_reader| svm_reader.get_block_at_slot(*slot, config))
1972 }
1973
1974 pub fn get_genesis_hash_local(&self) -> SvmAccessContext<Hash> {
1975 self.with_contextualized_svm_reader(|svm_reader| svm_reader.genesis_config.hash())
1976 }
1977
1978 pub async fn get_genesis_hash(
1979 &self,
1980 remote_ctx: &Option<SurfnetRemoteClient>,
1981 ) -> SurfpoolContextualizedResult<Hash> {
1982 if let Some(client) = remote_ctx {
1983 let remote_hash = client.get_genesis_hash().await?;
1984 Ok(self.with_contextualized_svm_reader(|_| remote_hash))
1985 } else {
1986 Ok(self.get_genesis_hash_local())
1987 }
1988 }
1989}
1990
1991impl SurfnetSvmLocker {
1993 pub fn simnet_events_tx(&self) -> Sender<SimnetEvent> {
1995 self.with_svm_reader(|svm_reader| svm_reader.simnet_events_tx.clone())
1996 }
1997
1998 pub fn get_epoch_info(&self) -> EpochInfo {
2000 self.with_svm_reader(|svm_reader| svm_reader.latest_epoch_info.clone())
2001 }
2002
2003 pub fn get_latest_absolute_slot(&self) -> Slot {
2005 self.with_svm_reader(|svm_reader| svm_reader.get_latest_absolute_slot())
2006 }
2007
2008 pub fn get_latest_blockhash(&self, config: &CommitmentConfig) -> Option<Hash> {
2010 let slot = self.get_slot_for_commitment(config);
2011 self.with_svm_reader(|svm_reader| svm_reader.blockhash_for_slot(slot))
2012 }
2013
2014 pub fn get_slot_for_commitment(&self, commitment: &CommitmentConfig) -> Slot {
2015 self.with_svm_reader(|svm_reader| {
2016 let slot = svm_reader.get_latest_absolute_slot();
2017 match commitment.commitment {
2018 CommitmentLevel::Processed => slot,
2019 CommitmentLevel::Confirmed => slot.saturating_sub(1),
2020 CommitmentLevel::Finalized => slot.saturating_sub(FINALIZATION_SLOT_THRESHOLD),
2021 }
2022 })
2023 }
2024
2025 pub fn airdrop(&self, pubkey: &Pubkey, lamports: u64) -> TransactionResult {
2027 self.with_svm_writer(|svm_writer| svm_writer.airdrop(pubkey, lamports))
2028 }
2029
2030 pub fn airdrop_pubkeys(&self, lamports: u64, addresses: &[Pubkey]) {
2032 self.with_svm_writer(|svm_writer| svm_writer.airdrop_pubkeys(lamports, addresses))
2033 }
2034
2035 pub fn confirm_current_block(&self) -> SurfpoolResult<()> {
2037 self.with_svm_writer(|svm_writer| svm_writer.confirm_current_block())
2038 }
2039
2040 pub fn subscribe_for_signature_updates(
2042 &self,
2043 signature: &Signature,
2044 subscription_type: SignatureSubscriptionType,
2045 ) -> Receiver<(Slot, Option<TransactionError>)> {
2046 self.with_svm_writer(|svm_writer| {
2047 svm_writer.subscribe_for_signature_updates(signature, subscription_type.clone())
2048 })
2049 }
2050
2051 pub fn subscribe_for_account_updates(
2053 &self,
2054 account_pubkey: &Pubkey,
2055 encoding: Option<UiAccountEncoding>,
2056 ) -> Receiver<UiAccount> {
2057 self.with_svm_writer(|svm_writer| {
2059 svm_writer.subscribe_for_account_updates(account_pubkey, encoding)
2060 })
2061 }
2062
2063 pub fn subscribe_for_slot_updates(&self) -> Receiver<SlotInfo> {
2065 self.with_svm_writer(|svm_writer| svm_writer.subscribe_for_slot_updates())
2066 }
2067
2068 pub fn subscribe_for_logs_updates(
2070 &self,
2071 commitment_level: &CommitmentLevel,
2072 filter: &RpcTransactionLogsFilter,
2073 ) -> Receiver<(Slot, RpcLogsResponse)> {
2074 self.with_svm_writer(|svm_writer| {
2075 svm_writer.subscribe_for_logs_updates(commitment_level, filter)
2076 })
2077 }
2078
2079 fn snapshot_get_account_result(
2080 &self,
2081 capture: &mut BTreeMap<Pubkey, Option<UiAccount>>,
2082 result: GetAccountResult,
2083 encoding: Option<UiAccountEncoding>,
2084 ) {
2085 let config = RpcAccountInfoConfig {
2086 encoding,
2087 ..Default::default()
2088 };
2089 match result {
2090 GetAccountResult::None(pubkey) => {
2091 capture.insert(pubkey, None);
2092 }
2093 GetAccountResult::FoundAccount(pubkey, account, _) => {
2094 let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2095 svm_reader.account_to_rpc_keyed_account(&pubkey, &account, &config, None)
2096 });
2097 capture.insert(pubkey, Some(rpc_keyed_account.account));
2098 }
2099 GetAccountResult::FoundTokenAccount((pubkey, account), (mint_pubkey, mint_account)) => {
2100 let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2101 svm_reader.account_to_rpc_keyed_account(
2102 &pubkey,
2103 &account,
2104 &config,
2105 Some(mint_pubkey),
2106 )
2107 });
2108 capture.insert(pubkey, Some(rpc_keyed_account.account));
2109 if let Some(mint_account) = mint_account {
2110 let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2111 svm_reader.account_to_rpc_keyed_account(
2112 &mint_pubkey,
2113 &mint_account,
2114 &config,
2115 None,
2116 )
2117 });
2118 capture.insert(mint_pubkey, Some(rpc_keyed_account.account));
2119 }
2120 }
2121 GetAccountResult::FoundProgramAccount(
2122 (pubkey, account),
2123 (data_pubkey, data_account),
2124 ) => {
2125 let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2126 svm_reader.account_to_rpc_keyed_account(&pubkey, &account, &config, None)
2127 });
2128 capture.insert(pubkey, Some(rpc_keyed_account.account));
2129 if let Some(data_account) = data_account {
2130 let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2131 svm_reader.account_to_rpc_keyed_account(
2132 &data_pubkey,
2133 &data_account,
2134 &config,
2135 None,
2136 )
2137 });
2138 capture.insert(data_pubkey, Some(rpc_keyed_account.account));
2139 }
2140 }
2141 }
2142 }
2143}
2144
2145fn apply_rpc_filters(account_data: &[u8], filters: &[RpcFilterType]) -> SurfpoolResult<bool> {
2147 for filter in filters {
2148 match filter {
2149 RpcFilterType::DataSize(size) => {
2150 if account_data.len() as u64 != *size {
2151 return Ok(false);
2152 }
2153 }
2154 RpcFilterType::Memcmp(memcmp_filter) => {
2155 if !memcmp_filter.bytes_match(account_data) {
2157 return Ok(false); }
2159 }
2160 RpcFilterType::TokenAccountState => {
2161 return Err(SurfpoolError::internal(
2162 "TokenAccountState filter is not supported",
2163 ));
2164 }
2165 }
2166 }
2167 Ok(true)
2168}
2169
2170pub fn is_supported_token_program(program_id: &Pubkey) -> bool {
2172 *program_id == spl_token::ID || *program_id == spl_token_2022::ID
2173}
2174
2175fn update_programdata_account(
2176 program_id: &Pubkey,
2177 programdata_account: &mut Account,
2178 new_authority: Option<Pubkey>,
2179) -> SurfpoolResult<Option<Pubkey>> {
2180 let upgradeable_loader_state =
2181 bincode::deserialize::<UpgradeableLoaderState>(&programdata_account.data).map_err(|e| {
2182 SurfpoolError::invalid_program_account(
2183 &program_id,
2184 format!("Failed to serialize program data: {}", e),
2185 )
2186 })?;
2187 if let UpgradeableLoaderState::ProgramData {
2188 upgrade_authority_address,
2189 slot,
2190 } = upgradeable_loader_state
2191 {
2192 let offset = if upgrade_authority_address.is_some() {
2193 UpgradeableLoaderState::size_of_programdata_metadata()
2194 } else {
2195 UpgradeableLoaderState::size_of_programdata_metadata()
2196 - serialized_size(&Pubkey::default()).unwrap() as usize
2197 };
2198
2199 let mut data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
2200 upgrade_authority_address: new_authority,
2201 slot,
2202 })
2203 .map_err(|e| {
2204 SurfpoolError::invalid_program_account(
2205 &program_id,
2206 format!("Failed to serialize program data: {}", e),
2207 )
2208 })?;
2209
2210 data.append(&mut programdata_account.data[offset..].to_vec());
2211
2212 programdata_account.data = data;
2213
2214 Ok(upgrade_authority_address)
2215 } else {
2216 return Err(SurfpoolError::invalid_program_account(
2217 &program_id,
2218 "Invalid program data account",
2219 ));
2220 }
2221}
2222
2223pub fn format_ui_amount_string(amount: u64, decimals: u8) -> String {
2224 let formatted_amount = if decimals > 0 {
2225 let divisor = 10u64.pow(decimals as u32);
2226 format!(
2227 "{:.decimals$}",
2228 amount as f64 / divisor as f64,
2229 decimals = decimals as usize
2230 )
2231 } else {
2232 amount.to_string()
2233 };
2234 formatted_amount
2235}
2236
2237pub fn format_ui_amount(amount: u64, decimals: u8) -> f64 {
2238 if decimals > 0 {
2239 let divisor = 10u64.pow(decimals as u32);
2240 amount as f64 / divisor as f64
2241 } else {
2242 amount as f64
2243 }
2244}