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}