sol-chainsaw 0.0.2

Deserializing Solana accounts using their progam IDL
Documentation
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

use bson::RawBson;
use solana_idl::Idl;

use crate::{
    bson::{
        BsonIdlTypeDefinitionDeserializer, BsonSerializationOpts,
        BsonTypeDefinitionDeserializerMap,
    },
    errors::{ChainsawError, ChainsawResult},
    utils::{account_discriminator, DiscriminatorBytes},
    IdlProvider,
};

/// Setup to  deserialize accounts for a given program. The accounts are expected to have been
/// serialized using the [borsh] format.
///
/// Uses deserializers defined inside [deserializer] modules under the hood in order to resolve the
/// appropriate [borsh] deserializers for each field.
pub struct BsonAccountsDeserializer<'opts> {
    /// The parsed [Idl] of the program
    pub idl: Idl,

    /// The [IdlProvider] of the program
    pub provider: IdlProvider,

    /// The deserializers for accounts of this program keyed by the discriminator of each account
    /// type.
    pub account_deserializers:
        HashMap<DiscriminatorBytes, BsonIdlTypeDefinitionDeserializer<'opts>>,

    /// Allows looking up a account names by discriminator.
    pub account_names: HashMap<DiscriminatorBytes, String>,

    /// Allows looking up a discriminator for an acount by name.
    pub account_discriminators: HashMap<String, DiscriminatorBytes>,

    /// Map of defined type deserializers. Defined types are be nested inside accounts.
    pub type_map: BsonTypeDefinitionDeserializerMap<'opts>,

    /// The [BsonSerializationOpts] specifying how specific data types should be deserialized.
    pub serialization_opts: &'opts BsonSerializationOpts,
}

impl<'opts> BsonAccountsDeserializer<'opts> {
    /// Tries to create an [AccounbtDeserializer] by parsing the [Idl].
    /// Fails if the IDL could not be parsed.
    ///
    /// - [json} the IDL definition in JSON format
    /// - [provider] the provider used to create the IDL
    /// - [serialization_opts] specifying how specific data types should be deserialized.
    pub fn try_from_idl(
        json: &str,
        provider: IdlProvider,
        serialization_opts: &'opts BsonSerializationOpts,
    ) -> ChainsawResult<Self> {
        let idl: Idl = serde_json::from_str(json)?;
        Ok(Self::from_idl(idl, provider, serialization_opts))
    }

    /// Creates an [BsonAccountsDeserializer] from the provided [Idl]
    /// Fails if the IDL could not be parsed.
    ///
    /// - [idl} the IDL definition
    /// - [provider] the provider used to create the IDL
    /// - [serialization_opts] specifying how specific data types should be deserialized.
    pub fn from_idl(
        idl: Idl,
        provider: IdlProvider,
        serialization_opts: &'opts BsonSerializationOpts,
    ) -> Self {
        // NOTE: unfortunately there is lots of duplication from ../json/json_accounts_deserializer.rs
        // in this method which is not easy to avoid.
        // Read the code there for more pointers about assumptions made (which are not repeated
        // here).
        let mut account_deserializers = HashMap::new();
        let mut account_discriminators = HashMap::new();
        let type_map = Arc::new(Mutex::new(HashMap::new()));

        for type_definition in &idl.types {
            let instance = BsonIdlTypeDefinitionDeserializer::new(
                type_definition,
                type_map.clone(),
                serialization_opts,
            );
            type_map
                .lock()
                .unwrap()
                .insert(instance.name.clone(), instance);
        }

        for type_definition in &idl.accounts {
            let type_deserializer =
                BsonIdlTypeDefinitionDeserializer::<'opts>::new(
                    type_definition,
                    type_map.clone(),
                    serialization_opts,
                );
            let discriminator = account_discriminator(&type_definition.name);
            account_deserializers.insert(discriminator, type_deserializer);

            account_discriminators
                .insert(type_definition.name.clone(), discriminator);
        }

        let account_names = account_discriminators
            .iter()
            .map(|(name, discriminator)| (*discriminator, name.clone()))
            .collect();

        Self {
            idl,
            provider,
            type_map,
            serialization_opts,
            account_deserializers,
            account_discriminators,
            account_names,
        }
    }

    /// Deserializes an account from the provided data.
    ///
    /// This is the common way of resolving an account raw bson and using it to deserialize
    /// account data.
    /// It expects the first 8 bytes of data to hold the account discriminator as is the case for
    /// anchor accounts.
    /// For all other accounts use [deserialize_account_data_by_name] instead.
    pub fn deserialize_account_data(
        &self,
        account_data: &mut &[u8],
    ) -> ChainsawResult<RawBson> {
        let discriminator = &account_data[..8];
        let deserializer = self
            .account_deserializers
            .get(discriminator)
            .ok_or_else(|| {
                ChainsawError::UnknownDiscriminatedAccount(format!(
                    "disciminator: {:?}",
                    discriminator
                ))
            })?;

        let data = &mut &account_data[8..];
        deserializer.deserialize(data)
    }

    /// Deserializes an account from the provided data.
    ///
    /// This method expects account data to **not** be prefixed with 8 bytes of discriminator data.
    /// Instead it derives that discriminator from the provided account name and then looks up the
    /// json.
    pub fn deserialize_account_data_by_name(
        &self,
        account_data: &mut &[u8],
        account_name: &str,
    ) -> ChainsawResult<RawBson> {
        let discriminator = account_discriminator(account_name);
        let deserializer =
            self.account_deserializers.get(&discriminator).ok_or_else(
                || ChainsawError::UnknownAccount(account_name.to_string()),
            )?;

        deserializer.deserialize(account_data)
    }

    /// Resolves the account name for the provided discriminator.
    pub fn account_name(
        &self,
        discriminator: &DiscriminatorBytes,
    ) -> Option<&str> {
        self.account_names.get(discriminator).map(|s| s.as_str())
    }
}