1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use borsh::BorshDeserialize;
use solana_program::account_info::AccountInfo;

use crate::{
    accounts::{BaseAssetV1, PluginHeaderV1, PluginRegistryV1},
    errors::MplCoreError,
    types::{Plugin, PluginAuthority, PluginType, RegistryRecord},
    AttributesPlugin, BaseAuthority, BasePlugin, BurnDelegatePlugin, DataBlob,
    FreezeDelegatePlugin, PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin,
    PermanentTransferDelegatePlugin, PluginsList, RoyaltiesPlugin, SolanaAccount,
    TransferDelegatePlugin, UpdateDelegatePlugin,
};

/// Fetch the plugin from the registry.
pub fn fetch_plugin<T: DataBlob + SolanaAccount, U: BorshDeserialize>(
    account: &AccountInfo,
    plugin_type: PluginType,
) -> Result<(PluginAuthority, U, usize), std::io::Error> {
    let asset = T::load(account, 0)?;

    if asset.get_size() == account.data_len() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            MplCoreError::PluginNotFound.to_string(),
        ));
    }

    let header = PluginHeaderV1::load(account, asset.get_size())?;
    let PluginRegistryV1 { registry, .. } =
        PluginRegistryV1::load(account, header.plugin_registry_offset as usize)?;

    // Find the plugin in the registry.
    let registry_record = registry
        .iter()
        .find(|record| record.plugin_type == plugin_type)
        .ok_or(std::io::Error::new(
            std::io::ErrorKind::Other,
            MplCoreError::PluginNotFound.to_string(),
        ))?;

    // Deserialize the plugin.
    let plugin =
        Plugin::deserialize(&mut &(*account.data).borrow()[(registry_record.offset as usize)..])?;

    if PluginType::from(&plugin) != plugin_type {
        return Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            MplCoreError::PluginNotFound.to_string(),
        ));
    }

    let inner = U::deserialize(
        &mut &(*account.data).borrow()[registry_record.offset.checked_add(1).ok_or(
            std::io::Error::new(
                std::io::ErrorKind::Other,
                MplCoreError::NumericalOverflow.to_string(),
            ),
        )? as usize..],
    )?;

    // Return the plugin and its authority.
    Ok((
        registry_record.authority.clone(),
        inner,
        registry_record.offset as usize,
    ))
}

/// Fetch the plugin registry.
pub fn fetch_plugins(account: &[u8]) -> Result<Vec<RegistryRecord>, std::io::Error> {
    let asset = BaseAssetV1::from_bytes(account)?;

    let header = PluginHeaderV1::from_bytes(&account[asset.get_size()..])?;
    let PluginRegistryV1 { registry, .. } =
        PluginRegistryV1::from_bytes(&account[(header.plugin_registry_offset as usize)..])?;

    Ok(registry)
}

/// Create plugin header and registry if it doesn't exist
pub fn list_plugins(account: &[u8]) -> Result<Vec<PluginType>, std::io::Error> {
    let asset = BaseAssetV1::from_bytes(account)?;

    let header = PluginHeaderV1::from_bytes(&account[asset.get_size()..])?;
    let PluginRegistryV1 { registry, .. } =
        PluginRegistryV1::from_bytes(&account[(header.plugin_registry_offset as usize)..])?;

    Ok(registry
        .iter()
        .map(|registry_record| registry_record.plugin_type.clone())
        .collect())
}

pub fn registry_records_to_plugin_list(
    registry_records: &[RegistryRecord],
    account_data: &[u8],
) -> Result<PluginsList, std::io::Error> {
    let result = registry_records
        .iter()
        .try_fold(PluginsList::default(), |mut acc, record| {
            let authority: BaseAuthority = record.authority.clone().into();
            let base = BasePlugin {
                authority,
                offset: Some(record.offset),
            };
            let plugin = Plugin::deserialize(&mut &account_data[record.offset as usize..])?;

            match plugin {
                Plugin::Royalties(royalties) => {
                    acc.royalties = Some(RoyaltiesPlugin { base, royalties });
                }
                Plugin::FreezeDelegate(freeze_delegate) => {
                    acc.freeze_delegate = Some(FreezeDelegatePlugin {
                        base,
                        freeze_delegate,
                    });
                }
                Plugin::BurnDelegate(burn_delegate) => {
                    acc.burn_delegate = Some(BurnDelegatePlugin {
                        base,
                        burn_delegate,
                    });
                }
                Plugin::TransferDelegate(transfer_delegate) => {
                    acc.transfer_delegate = Some(TransferDelegatePlugin {
                        base,
                        transfer_delegate,
                    });
                }
                Plugin::UpdateDelegate(update_delegate) => {
                    acc.update_delegate = Some(UpdateDelegatePlugin {
                        base,
                        update_delegate,
                    });
                }
                Plugin::PermanentFreezeDelegate(permanent_freeze_delegate) => {
                    acc.permanent_freeze_delegate = Some(PermanentFreezeDelegatePlugin {
                        base,
                        permanent_freeze_delegate,
                    });
                }
                Plugin::Attributes(attributes) => {
                    acc.attributes = Some(AttributesPlugin { base, attributes });
                }
                Plugin::PermanentTransferDelegate(permanent_transfer_delegate) => {
                    acc.permanent_transfer_delegate = Some(PermanentTransferDelegatePlugin {
                        base,
                        permanent_transfer_delegate,
                    })
                }
                Plugin::PermanentBurnDelegate(permanent_burn_delegate) => {
                    acc.permanent_burn_delegate = Some(PermanentBurnDelegatePlugin {
                        base,
                        permanent_burn_delegate,
                    })
                }
            };

            Ok(acc)
        });

    result
}