surfpool_types/
types.rs

1use std::{
2    cmp::Ordering,
3    collections::{BTreeMap, HashMap},
4    fmt,
5    path::PathBuf,
6    str::FromStr,
7};
8
9use blake3::Hash;
10use chrono::{DateTime, Local};
11use crossbeam_channel::{Receiver, Sender};
12// use litesvm::types::TransactionMetadata;
13use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
14use serde_with::{BytesOrString, serde_as};
15use solana_account::Account;
16use solana_account_decoder_client_types::{UiAccount, UiAccountEncoding};
17use solana_clock::{Clock, Epoch, Slot};
18use solana_epoch_info::EpochInfo;
19use solana_message::inner_instruction::InnerInstructionsList;
20use solana_pubkey::Pubkey;
21use solana_signature::Signature;
22use solana_transaction::versioned::VersionedTransaction;
23use solana_transaction_context::TransactionReturnData;
24use solana_transaction_error::TransactionError;
25use txtx_addon_kit::indexmap::IndexMap;
26use txtx_addon_network_svm_types::subgraph::SubgraphRequest;
27use uuid::Uuid;
28
29pub const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com";
30pub const DEFAULT_RPC_PORT: u16 = 8899;
31pub const DEFAULT_WS_PORT: u16 = 8900;
32pub const DEFAULT_STUDIO_PORT: u16 = 8488;
33pub const CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED: u16 = 18488;
34pub const DEFAULT_NETWORK_HOST: &str = "127.0.0.1";
35pub const DEFAULT_SLOT_TIME_MS: u64 = 400;
36pub type Idl = anchor_lang_idl::types::Idl;
37
38#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
39pub struct TransactionMetadata {
40    pub signature: Signature,
41    pub logs: Vec<String>,
42    pub inner_instructions: InnerInstructionsList,
43    pub compute_units_consumed: u64,
44    pub return_data: TransactionReturnData,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub enum TransactionConfirmationStatus {
50    Processed,
51    Confirmed,
52    Finalized,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
56pub enum BlockProductionMode {
57    #[default]
58    Clock,
59    Transaction,
60    Manual,
61}
62
63#[derive(Debug)]
64pub enum SubgraphEvent {
65    EndpointReady,
66    InfoLog(DateTime<Local>, String),
67    ErrorLog(DateTime<Local>, String),
68    WarnLog(DateTime<Local>, String),
69    DebugLog(DateTime<Local>, String),
70    Shutdown,
71}
72
73impl SubgraphEvent {
74    pub fn info<S>(msg: S) -> Self
75    where
76        S: Into<String>,
77    {
78        Self::InfoLog(Local::now(), msg.into())
79    }
80
81    pub fn warn<S>(msg: S) -> Self
82    where
83        S: Into<String>,
84    {
85        Self::WarnLog(Local::now(), msg.into())
86    }
87
88    pub fn error<S>(msg: S) -> Self
89    where
90        S: Into<String>,
91    {
92        Self::ErrorLog(Local::now(), msg.into())
93    }
94
95    pub fn debug<S>(msg: S) -> Self
96    where
97        S: Into<String>,
98    {
99        Self::DebugLog(Local::now(), msg.into())
100    }
101}
102
103/// Result structure for compute units estimation.
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
105#[serde(rename_all = "camelCase")]
106pub struct ComputeUnitsEstimationResult {
107    pub success: bool,
108    pub compute_units_consumed: u64,
109    pub log_messages: Option<Vec<String>>,
110    pub error_message: Option<String>,
111}
112
113/// The struct for storing the profiling results.
114#[derive(Debug, Clone, PartialEq)]
115pub struct KeyedProfileResult {
116    pub slot: u64,
117    pub key: UuidOrSignature,
118    pub instruction_profiles: Option<Vec<ProfileResult>>,
119    pub transaction_profile: ProfileResult,
120    pub readonly_account_states: HashMap<Pubkey, Account>,
121}
122
123impl KeyedProfileResult {
124    pub fn new(
125        slot: u64,
126        key: UuidOrSignature,
127        instruction_profiles: Option<Vec<ProfileResult>>,
128        transaction_profile: ProfileResult,
129        readonly_account_states: HashMap<Pubkey, Account>,
130    ) -> Self {
131        Self {
132            slot,
133            key,
134            instruction_profiles,
135            transaction_profile,
136            readonly_account_states,
137        }
138    }
139}
140
141#[derive(Debug, Clone, PartialEq)]
142pub struct ProfileResult {
143    pub pre_execution_capture: ExecutionCapture,
144    pub post_execution_capture: ExecutionCapture,
145    pub compute_units_consumed: u64,
146    pub log_messages: Option<Vec<String>>,
147    pub error_message: Option<String>,
148}
149
150pub type ExecutionCapture = BTreeMap<Pubkey, Option<Account>>;
151
152impl ProfileResult {
153    pub fn new(
154        pre_execution_capture: ExecutionCapture,
155        post_execution_capture: ExecutionCapture,
156        compute_units_consumed: u64,
157        log_messages: Option<Vec<String>>,
158        error_message: Option<String>,
159    ) -> Self {
160        Self {
161            pre_execution_capture,
162            post_execution_capture,
163            compute_units_consumed,
164            log_messages,
165            error_message,
166        }
167    }
168}
169
170#[derive(Debug, Clone, PartialEq)]
171pub enum AccountProfileState {
172    Readonly,
173    Writable(AccountChange),
174}
175
176impl AccountProfileState {
177    pub fn new(
178        pubkey: Pubkey,
179        pre_account: Option<Account>,
180        post_account: Option<Account>,
181        readonly_accounts: &[Pubkey],
182    ) -> Self {
183        if readonly_accounts.contains(&pubkey) {
184            return AccountProfileState::Readonly;
185        }
186
187        match (pre_account, post_account) {
188            (None, Some(post_account)) => {
189                AccountProfileState::Writable(AccountChange::Create(post_account))
190            }
191            (Some(pre_account), None) => {
192                AccountProfileState::Writable(AccountChange::Delete(pre_account))
193            }
194            (Some(pre_account), Some(post_account)) if pre_account == post_account => {
195                AccountProfileState::Writable(AccountChange::Unchanged(Some(pre_account)))
196            }
197            (Some(pre_account), Some(post_account)) => {
198                AccountProfileState::Writable(AccountChange::Update(pre_account, post_account))
199            }
200            (None, None) => AccountProfileState::Writable(AccountChange::Unchanged(None)),
201        }
202    }
203}
204
205#[derive(Debug, Clone, PartialEq)]
206pub enum AccountChange {
207    Create(Account),
208    Update(Account, Account),
209    Delete(Account),
210    Unchanged(Option<Account>),
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214#[serde(rename_all = "camelCase")]
215pub struct RpcProfileResultConfig {
216    pub encoding: Option<UiAccountEncoding>,
217    pub depth: Option<RpcProfileDepth>,
218}
219
220impl Default for RpcProfileResultConfig {
221    fn default() -> Self {
222        Self {
223            encoding: Some(UiAccountEncoding::JsonParsed),
224            depth: Some(RpcProfileDepth::default()),
225        }
226    }
227}
228
229#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub enum RpcProfileDepth {
232    Transaction,
233    #[default]
234    Instruction,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
238#[serde(rename_all = "camelCase")]
239pub struct UiKeyedProfileResult {
240    pub slot: u64,
241    pub key: UuidOrSignature,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub instruction_profiles: Option<Vec<UiProfileResult>>,
244    pub transaction_profile: UiProfileResult,
245    #[serde(with = "profile_state_map")]
246    pub readonly_account_states: IndexMap<Pubkey, UiAccount>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
250#[serde(rename_all = "camelCase")]
251pub struct UiProfileResult {
252    #[serde(with = "profile_state_map")]
253    pub account_states: IndexMap<Pubkey, UiAccountProfileState>,
254    pub compute_units_consumed: u64,
255    pub log_messages: Option<Vec<String>>,
256    pub error_message: Option<String>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
260#[serde(rename_all = "camelCase", tag = "type", content = "accountChange")]
261pub enum UiAccountProfileState {
262    Readonly,
263    Writable(UiAccountChange),
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
267#[serde(rename_all = "camelCase", tag = "type", content = "data")]
268pub enum UiAccountChange {
269    Create(UiAccount),
270    Update(UiAccount, UiAccount),
271    Delete(UiAccount),
272    /// The account didn't change. If [Some], this is the initial state. If [None], the account didn't exist before/after execution.
273    Unchanged(Option<UiAccount>),
274}
275
276/// P starts with 300 lamports
277/// Ix 1 Transfers 100 lamports to P
278/// Ix 2 Transfers 100 lamports to P
279///
280/// Profile result 1 is from executing just Ix 1
281/// AccountProfileState::Writable(P, AccountChange::Update( UiAccount { lamports: 300, ...}, UiAccount { lamports: 400, ... }))
282///
283/// Profile result 2 is from executing Ix 1 and Ix 2
284/// AccountProfileState::Writable(P, AccountChange::Update( UiAccount { lamports: 400, ...}, UiAccount { lamports: 500, ... }))
285
286pub mod profile_state_map {
287    use super::*;
288
289    pub fn serialize<S, T>(map: &IndexMap<Pubkey, T>, serializer: S) -> Result<S::Ok, S::Error>
290    where
291        S: Serializer,
292        T: Serialize,
293    {
294        let str_map: IndexMap<String, &T> = map.iter().map(|(k, v)| (k.to_string(), v)).collect();
295        str_map.serialize(serializer)
296    }
297
298    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<IndexMap<Pubkey, T>, D::Error>
299    where
300        D: Deserializer<'de>,
301        T: Deserialize<'de>,
302    {
303        let str_map: IndexMap<String, T> = IndexMap::deserialize(deserializer)?;
304        str_map
305            .into_iter()
306            .map(|(k, v)| {
307                Pubkey::from_str(&k)
308                    .map(|pk| (pk, v))
309                    .map_err(serde::de::Error::custom)
310            })
311            .collect()
312    }
313}
314
315#[derive(Debug, Clone)]
316pub enum SubgraphCommand {
317    CreateCollection(Uuid, SubgraphRequest, Sender<String>),
318    ObserveCollection(Receiver<DataIndexingCommand>),
319    Shutdown,
320}
321
322#[derive(Debug)]
323pub enum SimnetEvent {
324    Ready,
325    Connected(String),
326    Aborted(String),
327    Shutdown,
328    SystemClockUpdated(Clock),
329    ClockUpdate(ClockCommand),
330    EpochInfoUpdate(EpochInfo),
331    BlockHashExpired,
332    InfoLog(DateTime<Local>, String),
333    ErrorLog(DateTime<Local>, String),
334    WarnLog(DateTime<Local>, String),
335    DebugLog(DateTime<Local>, String),
336    PluginLoaded(String),
337    TransactionReceived(DateTime<Local>, VersionedTransaction),
338    TransactionProcessed(
339        DateTime<Local>,
340        TransactionMetadata,
341        Option<TransactionError>,
342    ),
343    AccountUpdate(DateTime<Local>, Pubkey),
344    TaggedProfile {
345        result: KeyedProfileResult,
346        tag: String,
347        timestamp: DateTime<Local>,
348    },
349    RunbookStarted(String),
350    RunbookCompleted(String),
351}
352
353impl SimnetEvent {
354    pub fn info<S>(msg: S) -> Self
355    where
356        S: Into<String>,
357    {
358        Self::InfoLog(Local::now(), msg.into())
359    }
360
361    pub fn warn<S>(msg: S) -> Self
362    where
363        S: Into<String>,
364    {
365        Self::WarnLog(Local::now(), msg.into())
366    }
367
368    pub fn error<S>(msg: S) -> Self
369    where
370        S: Into<String>,
371    {
372        Self::ErrorLog(Local::now(), msg.into())
373    }
374
375    pub fn debug<S>(msg: S) -> Self
376    where
377        S: Into<String>,
378    {
379        Self::DebugLog(Local::now(), msg.into())
380    }
381
382    pub fn transaction_processed(meta: TransactionMetadata, err: Option<TransactionError>) -> Self {
383        Self::TransactionProcessed(Local::now(), meta, err)
384    }
385
386    pub fn transaction_received(tx: VersionedTransaction) -> Self {
387        Self::TransactionReceived(Local::now(), tx)
388    }
389
390    pub fn account_update(pubkey: Pubkey) -> Self {
391        Self::AccountUpdate(Local::now(), pubkey)
392    }
393
394    pub fn tagged_profile(result: KeyedProfileResult, tag: String) -> Self {
395        Self::TaggedProfile {
396            result,
397            tag,
398            timestamp: Local::now(),
399        }
400    }
401
402    pub fn account_update_msg(&self) -> String {
403        match self {
404            SimnetEvent::AccountUpdate(_, pubkey) => {
405                format!("Account {} updated.", pubkey)
406            }
407            _ => unreachable!("This function should only be called for AccountUpdate events"),
408        }
409    }
410
411    pub fn epoch_info_update_msg(&self) -> String {
412        match self {
413            SimnetEvent::EpochInfoUpdate(epoch_info) => {
414                format!(
415                    "Datasource connection successful. Epoch {} / Slot index {} / Slot {}.",
416                    epoch_info.epoch, epoch_info.slot_index, epoch_info.absolute_slot
417                )
418            }
419            _ => unreachable!("This function should only be called for EpochInfoUpdate events"),
420        }
421    }
422
423    pub fn plugin_loaded_msg(&self) -> String {
424        match self {
425            SimnetEvent::PluginLoaded(plugin_name) => {
426                format!("Plugin {} successfully loaded.", plugin_name)
427            }
428            _ => unreachable!("This function should only be called for PluginLoaded events"),
429        }
430    }
431
432    pub fn clock_update_msg(&self) -> String {
433        match self {
434            SimnetEvent::SystemClockUpdated(clock) => {
435                format!("Clock ticking (epoch {}, slot {})", clock.epoch, clock.slot)
436            }
437            _ => {
438                unreachable!("This function should only be called for SystemClockUpdated events")
439            }
440        }
441    }
442}
443
444#[derive(Debug)]
445pub enum TransactionStatusEvent {
446    Success(TransactionConfirmationStatus),
447    SimulationFailure((TransactionError, TransactionMetadata)),
448    ExecutionFailure((TransactionError, TransactionMetadata)),
449    VerificationFailure(String),
450}
451
452#[derive(Debug)]
453pub enum SimnetCommand {
454    SlotForward(Option<Hash>),
455    SlotBackward(Option<Hash>),
456    CommandClock(ClockCommand),
457    UpdateInternalClock(Clock),
458    UpdateBlockProductionMode(BlockProductionMode),
459    TransactionReceived(
460        Option<Hash>,
461        VersionedTransaction,
462        Sender<TransactionStatusEvent>,
463        bool,
464    ),
465    Terminate(Option<Hash>),
466}
467
468#[derive(Debug)]
469pub enum ClockCommand {
470    Pause,
471    Resume,
472    Toggle,
473    UpdateSlotInterval(u64),
474}
475
476pub enum ClockEvent {
477    Tick,
478    ExpireBlockHash,
479}
480
481#[derive(Clone, Debug, Default, Serialize)]
482pub struct SanitizedConfig {
483    pub rpc_url: String,
484    pub ws_url: String,
485    pub rpc_datasource_url: String,
486    pub studio_url: String,
487    pub graphql_query_route_url: String,
488    pub version: String,
489    pub workspace: Option<String>,
490}
491
492#[derive(Clone, Debug, Default)]
493pub struct SurfpoolConfig {
494    pub simnets: Vec<SimnetConfig>,
495    pub rpc: RpcConfig,
496    pub subgraph: SubgraphConfig,
497    pub studio: StudioConfig,
498    pub plugin_config_path: Vec<PathBuf>,
499}
500
501#[derive(Clone, Debug)]
502pub struct SimnetConfig {
503    pub remote_rpc_url: String,
504    pub slot_time: u64,
505    pub block_production_mode: BlockProductionMode,
506    pub airdrop_addresses: Vec<Pubkey>,
507    pub airdrop_token_amount: u64,
508    pub expiry: Option<u64>,
509}
510
511impl Default for SimnetConfig {
512    fn default() -> Self {
513        Self {
514            remote_rpc_url: DEFAULT_RPC_URL.to_string(),
515            slot_time: DEFAULT_SLOT_TIME_MS, // Default to 400ms to match CLI default
516            block_production_mode: BlockProductionMode::Clock,
517            airdrop_addresses: vec![],
518            airdrop_token_amount: 0,
519            expiry: None,
520        }
521    }
522}
523
524impl SimnetConfig {
525    pub fn get_sanitized_datasource_url(&self) -> String {
526        self.remote_rpc_url
527            .split("?")
528            .map(|e| e.to_string())
529            .collect::<Vec<String>>()
530            .first()
531            .expect("datasource url invalid")
532            .to_string()
533    }
534}
535
536#[derive(Clone, Debug, Default)]
537pub struct SubgraphConfig {}
538
539#[derive(Clone, Debug)]
540pub struct RpcConfig {
541    pub bind_host: String,
542    pub bind_port: u16,
543    pub ws_port: u16,
544}
545
546impl RpcConfig {
547    pub fn get_rpc_base_url(&self) -> String {
548        format!("{}:{}", self.bind_host, self.bind_port)
549    }
550    pub fn get_ws_base_url(&self) -> String {
551        format!("{}:{}", self.bind_host, self.ws_port)
552    }
553}
554
555impl Default for RpcConfig {
556    fn default() -> Self {
557        Self {
558            bind_host: DEFAULT_NETWORK_HOST.to_string(),
559            bind_port: DEFAULT_RPC_PORT,
560            ws_port: DEFAULT_WS_PORT,
561        }
562    }
563}
564
565#[derive(Clone, Debug)]
566pub struct StudioConfig {
567    pub bind_host: String,
568    pub bind_port: u16,
569}
570
571impl StudioConfig {
572    pub fn get_studio_base_url(&self) -> String {
573        format!("{}:{}", self.bind_host, self.bind_port)
574    }
575}
576
577impl Default for StudioConfig {
578    fn default() -> Self {
579        Self {
580            bind_host: DEFAULT_NETWORK_HOST.to_string(),
581            bind_port: CHANGE_TO_DEFAULT_STUDIO_PORT_ONCE_SUPERVISOR_MERGED,
582        }
583    }
584}
585
586#[derive(Debug, Clone, Deserialize, Serialize)]
587pub struct SubgraphPluginConfig {
588    pub uuid: Uuid,
589    pub ipc_token: String,
590    pub subgraph_request: SubgraphRequest,
591}
592
593#[derive(Serialize, Deserialize, Clone, Debug)]
594pub struct SvmSimnetInitializationRequest {
595    pub domain: String,
596    pub block_production_mode: BlockProductionMode,
597    pub datasource_rpc_url: String,
598}
599
600#[derive(Serialize, Deserialize, Clone, Debug)]
601pub enum SvmSimnetCommand {
602    Init(SvmSimnetInitializationRequest),
603}
604
605#[derive(Serialize, Deserialize)]
606pub struct CreateNetworkRequest {
607    pub workspace_id: Uuid,
608    pub name: String,
609    pub description: Option<String>,
610    pub datasource_rpc_url: String,
611    pub block_production_mode: BlockProductionMode,
612}
613
614impl CreateNetworkRequest {
615    pub fn new(
616        workspace_id: Uuid,
617        name: String,
618        description: Option<String>,
619        datasource_rpc_url: String,
620        block_production_mode: BlockProductionMode,
621    ) -> Self {
622        Self {
623            workspace_id,
624            name,
625            description,
626            datasource_rpc_url,
627            block_production_mode,
628        }
629    }
630}
631
632#[derive(Serialize, Deserialize)]
633pub struct CreateNetworkResponse {
634    pub rpc_url: String,
635}
636
637#[derive(Serialize, Deserialize)]
638pub struct DeleteNetworkRequest {
639    pub workspace_id: Uuid,
640    pub network_id: Uuid,
641}
642
643impl DeleteNetworkRequest {
644    pub fn new(workspace_id: Uuid, network_id: Uuid) -> Self {
645        Self {
646            workspace_id,
647            network_id,
648        }
649    }
650}
651
652#[derive(Serialize, Deserialize)]
653pub struct DeleteNetworkResponse;
654
655#[serde_as]
656#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
657#[serde(rename_all = "camelCase")]
658pub struct AccountUpdate {
659    /// providing this value sets the lamports in the account
660    pub lamports: Option<u64>,
661    /// providing this value sets the data held in this account
662    #[serde_as(as = "Option<BytesOrString>")]
663    pub data: Option<Vec<u8>>,
664    ///  providing this value sets the program that owns this account. If executable, the program that loads this account.
665    pub owner: Option<String>,
666    /// providing this value sets whether this account's data contains a loaded program (and is now read-only)
667    pub executable: Option<bool>,
668    /// providing this value sets the epoch at which this account will next owe rent
669    pub rent_epoch: Option<Epoch>,
670}
671
672#[derive(Debug, Clone)]
673pub enum SetSomeAccount {
674    Account(String),
675    NoAccount,
676}
677
678impl<'de> Deserialize<'de> for SetSomeAccount {
679    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
680    where
681        D: Deserializer<'de>,
682    {
683        struct SetSomeAccountVisitor;
684
685        impl<'de> Visitor<'de> for SetSomeAccountVisitor {
686            type Value = SetSomeAccount;
687
688            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
689                formatter.write_str("a Pubkey String or the String 'null'")
690            }
691
692            fn visit_some<D_>(self, deserializer: D_) -> std::result::Result<Self::Value, D_::Error>
693            where
694                D_: Deserializer<'de>,
695            {
696                Deserialize::deserialize(deserializer).map(|v: String| match v.as_str() {
697                    "null" => SetSomeAccount::NoAccount,
698                    _ => SetSomeAccount::Account(v.to_string()),
699                })
700            }
701        }
702
703        deserializer.deserialize_option(SetSomeAccountVisitor)
704    }
705}
706
707impl Serialize for SetSomeAccount {
708    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
709    where
710        S: Serializer,
711    {
712        match self {
713            SetSomeAccount::Account(val) => serializer.serialize_str(val),
714            SetSomeAccount::NoAccount => serializer.serialize_str("null"),
715        }
716    }
717}
718
719#[serde_as]
720#[derive(Debug, Clone, Default, Serialize, Deserialize)]
721#[serde(rename_all = "camelCase")]
722pub struct TokenAccountUpdate {
723    /// providing this value sets the amount of the token in the account data
724    pub amount: Option<u64>,
725    /// providing this value sets the delegate of the token account
726    pub delegate: Option<SetSomeAccount>,
727    /// providing this value sets the state of the token account
728    pub state: Option<String>,
729    /// providing this value sets the amount authorized to the delegate
730    pub delegated_amount: Option<u64>,
731    /// providing this value sets the close authority of the token account
732    pub close_authority: Option<SetSomeAccount>,
733}
734
735// token supply update for set supply method in SVM tricks
736#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
737pub struct SupplyUpdate {
738    pub total: Option<u64>,
739    pub circulating: Option<u64>,
740    pub non_circulating: Option<u64>,
741    pub non_circulating_accounts: Option<Vec<String>>,
742}
743
744#[derive(Clone, Debug, PartialEq, Copy)]
745pub enum UuidOrSignature {
746    Uuid(Uuid),
747    Signature(Signature),
748}
749
750impl std::fmt::Display for UuidOrSignature {
751    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752        match self {
753            UuidOrSignature::Uuid(uuid) => write!(f, "{}", uuid),
754            UuidOrSignature::Signature(signature) => write!(f, "{}", signature),
755        }
756    }
757}
758
759impl<'de> Deserialize<'de> for UuidOrSignature {
760    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
761    where
762        D: Deserializer<'de>,
763    {
764        let s = String::deserialize(deserializer)?;
765
766        if let Ok(uuid) = Uuid::parse_str(&s) {
767            return Ok(UuidOrSignature::Uuid(uuid));
768        }
769
770        if let Ok(signature) = s.parse::<Signature>() {
771            return Ok(UuidOrSignature::Signature(signature));
772        }
773
774        Err(serde::de::Error::custom(
775            "expected a Uuid or a valid Solana Signature",
776        ))
777    }
778}
779
780impl<'de> Serialize for UuidOrSignature {
781    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
782    where
783        S: Serializer,
784    {
785        match self {
786            UuidOrSignature::Uuid(uuid) => serializer.serialize_str(&uuid.to_string()),
787            UuidOrSignature::Signature(signature) => {
788                serializer.serialize_str(&signature.to_string())
789            }
790        }
791    }
792}
793
794#[derive(Debug, Clone, Deserialize, Serialize)]
795pub enum DataIndexingCommand {
796    ProcessCollection(Uuid),
797    ProcessCollectionEntriesPack(Uuid, Vec<u8>),
798}
799
800// Define a wrapper struct
801#[derive(Debug, Clone)]
802pub struct VersionedIdl(pub Slot, pub Idl);
803
804// Implement ordering based on Slot
805impl PartialEq for VersionedIdl {
806    fn eq(&self, other: &Self) -> bool {
807        self.0 == other.0
808    }
809}
810
811impl Eq for VersionedIdl {}
812
813impl PartialOrd for VersionedIdl {
814    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
815        Some(self.cmp(other))
816    }
817}
818
819impl Ord for VersionedIdl {
820    fn cmp(&self, other: &Self) -> Ordering {
821        self.0.cmp(&other.0)
822    }
823}
824
825#[cfg(test)]
826mod tests {
827    use serde_json::json;
828    use solana_account_decoder_client_types::{ParsedAccount, UiAccountData};
829
830    use super::*;
831
832    #[test]
833    fn print_ui_keyed_profile_result() {
834        let pubkey = Pubkey::new_unique();
835        let owner = Pubkey::new_unique();
836        let readonly_account_state = UiAccount {
837            lamports: 100,
838            data: UiAccountData::Binary(
839                "ABCDEFG".into(),
840                solana_account_decoder_client_types::UiAccountEncoding::Base64,
841            ),
842            owner: owner.to_string(),
843            executable: false,
844            rent_epoch: 0,
845            space: Some(100),
846        };
847
848        let account_1 = UiAccount {
849            lamports: 100,
850            data: UiAccountData::Json(ParsedAccount {
851                program: "custom-program".into(),
852                parsed: json!({
853                    "field1": "value1",
854                    "field2": "value2"
855                }),
856                space: 50,
857            }),
858            owner: owner.to_string(),
859            executable: false,
860            rent_epoch: 0,
861            space: Some(100),
862        };
863
864        let account_2 = UiAccount {
865            lamports: 100,
866            data: UiAccountData::Json(ParsedAccount {
867                program: "custom-program".into(),
868                parsed: json!({
869                    "field1": "updated-value1",
870                    "field2": "updated-value2"
871                }),
872                space: 50,
873            }),
874            owner: owner.to_string(),
875            executable: false,
876            rent_epoch: 0,
877            space: Some(100),
878        };
879        let profile_result = UiKeyedProfileResult {
880            slot: 123,
881            key: UuidOrSignature::Uuid(Uuid::new_v4()),
882            instruction_profiles: Some(vec![
883                UiProfileResult {
884                    account_states: IndexMap::from_iter([
885                        (
886                            pubkey,
887                            UiAccountProfileState::Writable(UiAccountChange::Create(
888                                account_1.clone(),
889                            )),
890                        ),
891                        (owner, UiAccountProfileState::Readonly),
892                    ]),
893                    compute_units_consumed: 100,
894                    log_messages: Some(vec![
895                        "Log message: Creating Account".to_string(),
896                        "Log message: Account created".to_string(),
897                    ]),
898                    error_message: None,
899                },
900                UiProfileResult {
901                    account_states: IndexMap::from_iter([
902                        (
903                            pubkey,
904                            UiAccountProfileState::Writable(UiAccountChange::Update(
905                                account_1,
906                                account_2.clone(),
907                            )),
908                        ),
909                        (owner, UiAccountProfileState::Readonly),
910                    ]),
911                    compute_units_consumed: 100,
912                    log_messages: Some(vec![
913                        "Log message: Updating Account".to_string(),
914                        "Log message: Account updated".to_string(),
915                    ]),
916                    error_message: None,
917                },
918                UiProfileResult {
919                    account_states: IndexMap::from_iter([
920                        (
921                            pubkey,
922                            UiAccountProfileState::Writable(UiAccountChange::Delete(account_2)),
923                        ),
924                        (owner, UiAccountProfileState::Readonly),
925                    ]),
926                    compute_units_consumed: 100,
927                    log_messages: Some(vec![
928                        "Log message: Deleting Account".to_string(),
929                        "Log message: Account deleted".to_string(),
930                    ]),
931                    error_message: None,
932                },
933            ]),
934            transaction_profile: UiProfileResult {
935                account_states: IndexMap::from_iter([
936                    (
937                        pubkey,
938                        UiAccountProfileState::Writable(UiAccountChange::Unchanged(None)),
939                    ),
940                    (owner, UiAccountProfileState::Readonly),
941                ]),
942                compute_units_consumed: 300,
943                log_messages: Some(vec![
944                    "Log message: Creating Account".to_string(),
945                    "Log message: Account created".to_string(),
946                    "Log message: Updating Account".to_string(),
947                    "Log message: Account updated".to_string(),
948                    "Log message: Deleting Account".to_string(),
949                    "Log message: Account deleted".to_string(),
950                ]),
951                error_message: None,
952            },
953            readonly_account_states: IndexMap::from_iter([(owner, readonly_account_state)]),
954        };
955        println!("{}", serde_json::to_string_pretty(&profile_result).unwrap());
956    }
957}