sol_chainsaw/json/
json_accounts_deserializer.rs

1use std::{
2    collections::HashMap,
3    fmt::Write,
4    sync::{Arc, Mutex},
5};
6
7use solana_idl::Idl;
8
9use crate::{
10    errors::{ChainsawError, ChainsawResult},
11    json::{
12        JsonIdlTypeDefinitionDeserializer, JsonSerializationOpts,
13        JsonTypeDefinitionDeserializerMap,
14    },
15    utils::{account_discriminator, DiscriminatorBytes},
16    IdlProvider,
17};
18
19/// Setup to  deserialize accounts for a given program. The accounts are expected to have been
20/// serialized using the [borsh] format.
21///
22/// Uses deserializers defined inside [deserializer] modules under the hood in order to resolve the
23/// appropriate [borsh] deserializers for each field.
24pub struct JsonAccountsDeserializer<'opts> {
25    /// The parsed [Idl] of the program
26    pub idl: Idl,
27
28    /// The [IdlProvider] of the program
29    pub provider: IdlProvider,
30
31    /// The deserializers for accounts of this program keyed by the discriminator of each account
32    /// type.
33    pub account_deserializers:
34        HashMap<DiscriminatorBytes, JsonIdlTypeDefinitionDeserializer<'opts>>,
35
36    /// Allows looking up a discriminator for an account by name.
37    pub account_discriminators: HashMap<String, DiscriminatorBytes>,
38
39    /// Allows looking up a account names by discriminator.
40    pub account_names: HashMap<DiscriminatorBytes, String>,
41
42    /// Map of defined type deserializers. Defined types are be nested inside accounts.
43    pub type_map: JsonTypeDefinitionDeserializerMap<'opts>,
44
45    /// The [JsonSerializationOpts] specifying how specific data types should be deserialized.
46    pub serialization_opts: &'opts JsonSerializationOpts,
47}
48
49impl<'opts> JsonAccountsDeserializer<'opts> {
50    /// Tries to create an [AccounbtDeserializer] by parsing the [Idl].
51    /// Fails if the IDL could not be parsed.
52    ///
53    /// - [json} the IDL definition in JSON format
54    /// - [provider] the provider used to create the IDL
55    /// - [serialization_opts] specifying how specific data types should be deserialized.
56    pub fn try_from_idl(
57        json: &str,
58        provider: IdlProvider,
59        serialization_opts: &'opts JsonSerializationOpts,
60    ) -> ChainsawResult<Self> {
61        let idl: Idl = serde_json::from_str(json)?;
62        Ok(Self::from_idl(idl, provider, serialization_opts))
63    }
64
65    /// Creates an [AccounbtDeserializer] from the provided [Idl]
66    /// Fails if the IDL could not be parsed.
67    ///
68    /// - [idl} the IDL definition
69    /// - [provider] the provider used to create the IDL
70    /// - [serialization_opts] specifying how specific data types should be deserialized.
71    pub fn from_idl(
72        idl: Idl,
73        provider: IdlProvider,
74        serialization_opts: &'opts JsonSerializationOpts,
75    ) -> Self {
76        let mut account_deserializers = HashMap::new();
77        let mut account_discriminators = HashMap::new();
78        let type_map = Arc::new(Mutex::new(HashMap::new()));
79
80        for type_definition in &idl.types {
81            let instance = JsonIdlTypeDefinitionDeserializer::new(
82                type_definition,
83                type_map.clone(),
84                serialization_opts,
85            );
86            type_map
87                .lock()
88                .unwrap()
89                .insert(instance.name.clone(), instance);
90        }
91
92        for type_definition in &idl.accounts {
93            let type_deserializer =
94                JsonIdlTypeDefinitionDeserializer::<'opts>::new(
95                    type_definition,
96                    type_map.clone(),
97                    serialization_opts,
98                );
99            // NOTE: for now we assume that one account doesn't reference another
100            //       thus we don't include it in the lookup map for nested types
101            //       Similarly for instruction args once we support them
102            let discriminator = account_discriminator(&type_definition.name);
103            account_deserializers.insert(discriminator, type_deserializer);
104
105            // We expect less accounts to be looked up by name and are fine with an extra
106            // lookup instead of keeping another copy of the deserializers by name directly
107            account_discriminators
108                .insert(type_definition.name.clone(), discriminator);
109        }
110
111        let account_names = account_discriminators
112            .iter()
113            .map(|(name, discriminator)| (*discriminator, name.clone()))
114            .collect();
115
116        Self {
117            idl,
118            provider,
119            type_map,
120            serialization_opts,
121            account_deserializers,
122            account_discriminators,
123            account_names,
124        }
125    }
126
127    /// Deserializes an account from the provided data.
128    ///
129    /// This is the common way of resolving an account json and using it to deserialize
130    /// account data.
131    /// It expects the first 8 bytes of data to hold the account discriminator as is the case for
132    /// anchor accounts.
133    /// For all other accounts use [deserialize_account_data_by_name] instead.
134    pub fn deserialize_account_data<W: Write>(
135        &self,
136        account_data: &mut &[u8],
137        f: &mut W,
138    ) -> ChainsawResult<()> {
139        let discriminator = &account_data[..8];
140        let deserializer = self
141            .account_deserializers
142            .get(discriminator)
143            .ok_or_else(|| {
144                ChainsawError::UnknownDiscriminatedAccount(format!(
145                    "disciminator: {:?}",
146                    discriminator
147                ))
148            })?;
149
150        let data = &mut &account_data[8..];
151        deserializer.deserialize(f, data)
152    }
153
154    /// Deserializes an account from the provided data.
155    ///
156    /// This method expects account data to **not** be prefixed with 8 bytes of discriminator data.
157    /// Instead it derives that discriminator from the provided account name and then looks up the
158    /// json.
159    pub fn deserialize_account_data_by_name<W: Write>(
160        &self,
161        account_data: &mut &[u8],
162        account_name: &str,
163        f: &mut W,
164    ) -> ChainsawResult<()> {
165        let discriminator = account_discriminator(account_name);
166        let deserializer =
167            self.account_deserializers.get(&discriminator).ok_or_else(
168                || ChainsawError::UnknownAccount(account_name.to_string()),
169            )?;
170
171        deserializer.deserialize(f, account_data)
172    }
173
174    /// Resolves the account name for the provided discriminator.
175    pub fn account_name(
176        &self,
177        discriminator: &DiscriminatorBytes,
178    ) -> Option<&str> {
179        self.account_names.get(discriminator).map(|s| s.as_str())
180    }
181}