module_ntoken/states/
portfolio.rs

1//! State transition types
2use crate::constants::{constant::PUBKEY_SIZE, account_type::TYPE_ACCOUNT_PORTFOLIO_ACCOUNT, account_size::{PORTFOLIO_PREFIX, ASSET_LEN}};
3use arrayref::{array_ref, array_refs};
4use serde::{Deserialize, Serialize};
5use solana_program::{
6    entrypoint_deprecated::ProgramResult,
7    program_error::ProgramError,
8    program_pack::{IsInitialized, Sealed},
9    pubkey::Pubkey,
10};
11use std::vec::Vec;
12
13const METADATAHASH_SIZE: usize = 16;
14const METADATAURL_SIZE: usize = 128;
15const TYPE_SIZE: usize = 1;
16const IS_INITIALIZED: usize = 1;
17const ASSET_DATA_LEN_SIZE: usize = 1;
18const NFT_TOKEN_SIZE: usize = 1;
19const VERSION_SIZE: usize = 1;
20const AMOUNT_SIZE: usize = 1;
21const PERIODE_SIZE: usize = 1;
22const PERCENTAGE_SIZE: usize = 1;
23
24/// struct of asset of ppm
25#[repr(C)]
26#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
27pub struct AssetStruct {
28    /// Amout of asset address.
29    pub amount: u8,
30    /// The address of asset.
31    pub address_asset: Pubkey,
32    /// Periode for this asset.
33    pub periode: u8,
34    /// Asset to sold into asset.
35    pub asset_to_sold_into_asset: Pubkey,
36    /// Percentage of asset.
37    pub percentage: u8,
38}
39
40impl AssetStruct {
41    /// Add new asset to portfolio
42    pub fn add_new_asset(
43        &mut self,
44        amount: u8,
45        address_asset: Pubkey,
46        periode: u8,
47        asset_to_sold_into_asset: Pubkey,
48        percentage: u8,
49    ) -> ProgramResult {
50        self.amount = amount;
51        self.address_asset = address_asset;
52        self.periode = periode;
53        self.asset_to_sold_into_asset = asset_to_sold_into_asset;
54        self.percentage = percentage;
55
56        Ok(())
57    }
58}
59
60/// Portfolio data.
61#[repr(C)]
62#[derive(Clone, Debug, Default, PartialEq)]
63pub struct Portfolio {
64    /// The type of account.
65    pub type_account: u8,
66    /// The creator of the portfolio was the only who can make changes on the portfolio account.
67    pub creator_portfolio: Pubkey,
68    /// Save the data of the new portfolio.
69    pub meta_data_url: Vec<u8>,
70    /// Hash of dataUrl to insure the immuability of data.
71    pub meta_data_hash: Vec<u8>,
72    /// Is initialized portfolio.
73    pub is_initialized: u8,
74    /// Length of assets by portfolio.
75    pub asset_data_len: u8,
76    /// Nft token stake.
77    pub nft_token: u8,
78    /// version actual for portfolio struct.
79    pub version: u8,
80    /// The address of the splm_stake.
81    pub splm_stake: Pubkey,
82    /// The address of the extended_data.
83    pub extended_data: Pubkey,
84    ///  Assets informations.
85    pub asset_data: Vec<AssetStruct>,
86}
87
88impl Sealed for Portfolio {}
89/// check if portfolio is initialized
90impl IsInitialized for Portfolio {
91    fn is_initialized(&self) -> bool {
92        return self.type_account == TYPE_ACCOUNT_PORTFOLIO_ACCOUNT;
93    }
94}
95
96/// trait for pack and unpack portfolio
97pub trait PackPortfolio {
98    /// unpack portfolio
99    fn unpack_portfolio(src: &[u8]) -> Result<Portfolio, ProgramError>;
100    /// pack portfolio
101    fn pack_portfolio(&self, dst: &mut [u8]);
102}
103
104// amount, address_asset, periode, asset_to_sold_into_asset
105impl PackPortfolio for Portfolio {
106    fn unpack_portfolio(src: &[u8]) -> Result<Self, ProgramError> {
107        let len_asset_nbre = (&src.len() - PORTFOLIO_PREFIX) / ASSET_LEN;
108
109        let src_fix = array_ref![&src, 0, PORTFOLIO_PREFIX];
110        let (
111            type_account,
112            creator_portfolio,
113            meta_data_url,
114            meta_data_hash,
115            is_initialized,
116            asset_data_len,
117            nft_token,
118            version,
119            splm_stake,
120            extended_data,
121        ) = array_refs![
122            src_fix,
123            TYPE_SIZE,
124            PUBKEY_SIZE,
125            METADATAURL_SIZE,
126            METADATAHASH_SIZE,
127            IS_INITIALIZED,
128            ASSET_DATA_LEN_SIZE,
129            NFT_TOKEN_SIZE,
130            VERSION_SIZE,
131            PUBKEY_SIZE,
132            PUBKEY_SIZE
133        ];
134
135        let len_data: usize = ASSET_LEN * len_asset_nbre;
136
137        let list_asset_data = &src[PORTFOLIO_PREFIX..PORTFOLIO_PREFIX + (len_data) as usize];
138
139        let mut asset_vec: Vec<AssetStruct> = Vec::with_capacity(len_asset_nbre);
140
141        // extract the list of assets for portfolio
142        // len_asset_nbre is mutable contain the numbre of assets by portfolio
143        let mut offset = 0;
144        for _ in 0..len_asset_nbre {
145            let asset_data = array_ref![list_asset_data, offset, ASSET_LEN];
146            #[allow(clippy::ptr_offset_with_cast)]
147            let (amount, address_asset, periode, asset_to_sold_into_asset, percentage) = array_refs![
148                asset_data,
149                AMOUNT_SIZE,
150                PUBKEY_SIZE,
151                PERIODE_SIZE,
152                PUBKEY_SIZE,
153                PERCENTAGE_SIZE
154            ];
155            asset_vec.push(AssetStruct {
156                amount: u8::from_le_bytes(*amount),
157                address_asset: Pubkey::new_from_array(*address_asset),
158                periode: u8::from_le_bytes(*periode),
159                asset_to_sold_into_asset: Pubkey::new_from_array(*asset_to_sold_into_asset),
160                percentage: u8::from_le_bytes(*percentage),
161            });
162            offset += ASSET_LEN;
163        }
164
165        return Ok(Portfolio {
166            type_account: u8::from_le_bytes(*type_account),
167            creator_portfolio: Pubkey::new(creator_portfolio),
168            meta_data_url: meta_data_url.to_vec(),
169            meta_data_hash: meta_data_hash.to_vec(),
170            is_initialized: u8::from_le_bytes(*is_initialized),
171            asset_data_len: u8::from_le_bytes(*asset_data_len),
172            nft_token: u8::from_le_bytes(*nft_token),
173            version: u8::from_le_bytes(*version),
174            splm_stake: Pubkey::new(splm_stake),
175            extended_data: Pubkey::new(extended_data),
176            asset_data: asset_vec.to_vec(),
177        });
178    }
179
180    fn pack_portfolio(&self, dst: &mut [u8]) {
181        let Portfolio {
182            ref creator_portfolio,
183            meta_data_url,
184            meta_data_hash,
185            type_account,
186            is_initialized,
187            asset_data_len,
188            nft_token,
189            version,
190            splm_stake,
191            extended_data,
192            asset_data,
193        } = self;
194
195        let asset_data_len_usize = *asset_data_len as usize;
196        let len_data_asset = ASSET_LEN * asset_data_len_usize;
197
198        let mut buffer = [0; PORTFOLIO_PREFIX + ASSET_LEN * 10];
199
200        buffer[0] = *type_account;
201        let creator_portfolio_range = TYPE_SIZE..TYPE_SIZE + PUBKEY_SIZE;
202        buffer[creator_portfolio_range].clone_from_slice(creator_portfolio.as_ref());
203        let meta_data_url_range =
204            TYPE_SIZE + PUBKEY_SIZE..TYPE_SIZE + PUBKEY_SIZE + METADATAURL_SIZE;
205        buffer[meta_data_url_range].clone_from_slice(meta_data_url);
206
207        let meta_data_hash_range = TYPE_SIZE + PUBKEY_SIZE + METADATAURL_SIZE
208            ..TYPE_SIZE + PUBKEY_SIZE + METADATAURL_SIZE + METADATAHASH_SIZE;
209        buffer[meta_data_hash_range].clone_from_slice(&meta_data_hash);
210
211        let is_initialized_range = TYPE_SIZE + PUBKEY_SIZE + METADATAURL_SIZE + METADATAHASH_SIZE;
212        buffer[is_initialized_range] = *is_initialized;
213        let asset_data_len_range =
214            TYPE_SIZE + PUBKEY_SIZE + METADATAURL_SIZE + METADATAHASH_SIZE + IS_INITIALIZED;
215        buffer[asset_data_len_range] = *asset_data_len;
216
217        let nft_token_range = TYPE_SIZE
218            + PUBKEY_SIZE
219            + METADATAURL_SIZE
220            + METADATAHASH_SIZE
221            + IS_INITIALIZED
222            + ASSET_DATA_LEN_SIZE;
223        buffer[nft_token_range] = *nft_token;
224        let version_range = TYPE_SIZE
225            + PUBKEY_SIZE
226            + METADATAURL_SIZE
227            + METADATAHASH_SIZE
228            + IS_INITIALIZED
229            + ASSET_DATA_LEN_SIZE
230            + NFT_TOKEN_SIZE;
231        buffer[version_range] = *version;
232        let splm_stake_range = TYPE_SIZE
233            + PUBKEY_SIZE
234            + METADATAURL_SIZE
235            + METADATAHASH_SIZE
236            + IS_INITIALIZED
237            + ASSET_DATA_LEN_SIZE
238            + NFT_TOKEN_SIZE
239            + VERSION_SIZE
240            ..TYPE_SIZE
241                + PUBKEY_SIZE
242                + METADATAURL_SIZE
243                + METADATAHASH_SIZE
244                + IS_INITIALIZED
245                + ASSET_DATA_LEN_SIZE
246                + NFT_TOKEN_SIZE
247                + VERSION_SIZE
248                + PUBKEY_SIZE;
249        buffer[splm_stake_range].clone_from_slice(splm_stake.as_ref());
250        let extended_data_range = TYPE_SIZE
251            + PUBKEY_SIZE
252            + METADATAURL_SIZE
253            + METADATAHASH_SIZE
254            + IS_INITIALIZED
255            + ASSET_DATA_LEN_SIZE
256            + NFT_TOKEN_SIZE
257            + VERSION_SIZE
258            + PUBKEY_SIZE
259            ..TYPE_SIZE
260                + PUBKEY_SIZE
261                + METADATAURL_SIZE
262                + METADATAHASH_SIZE
263                + IS_INITIALIZED
264                + ASSET_DATA_LEN_SIZE
265                + NFT_TOKEN_SIZE
266                + VERSION_SIZE
267                + PUBKEY_SIZE
268                + PUBKEY_SIZE;
269        buffer[extended_data_range].clone_from_slice(extended_data.as_ref());
270
271        let asset_vec_tmp = bincode::serialize(&asset_data).unwrap();
272
273        let mut asset_data_tmp = [0; ASSET_LEN * 10]; // MAXIMUM 10 ASSETS
274
275        let len_asset = asset_vec_tmp.len();
276
277        asset_data_tmp[0..len_data_asset as usize].clone_from_slice(&asset_vec_tmp[8..len_asset]); // 0..8 : 8 bytes contain length of struct
278
279        buffer[PORTFOLIO_PREFIX..PORTFOLIO_PREFIX + len_data_asset as usize]
280            .clone_from_slice(&asset_data_tmp[0..len_data_asset as usize]);
281
282        let mut buffer_tranformed: Vec<u8> =
283            Vec::with_capacity(PORTFOLIO_PREFIX + (len_data_asset as usize));
284        buffer_tranformed.resize(PORTFOLIO_PREFIX + (len_data_asset as usize), 0);
285        buffer_tranformed[0..PORTFOLIO_PREFIX + len_data_asset as usize]
286            .copy_from_slice(&buffer[0..PORTFOLIO_PREFIX + (len_data_asset as usize)]);
287
288        dst[0..PORTFOLIO_PREFIX + len_data_asset as usize].copy_from_slice(&buffer_tranformed);
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    #[test]
296    fn test_pack_asset_portfolio() {
297        let amount = 12;
298        let address_asset = Pubkey::new_unique();
299        let periode = 2;
300        let asset_to_sold_into_asset = Pubkey::new_unique();
301        let percentage = 1;
302        let number_assets: u8 = 1;
303        let asset = AssetStruct {
304            amount,
305            address_asset,
306            periode,
307            asset_to_sold_into_asset,
308            percentage,
309        };
310        let meta_data_url = [1; METADATAURL_SIZE];
311        let meta_data_hash = [1; METADATAHASH_SIZE];
312        let mut asset_data = Vec::new();
313        asset_data.push(asset);
314        let portfolio = Portfolio {
315            type_account: TYPE_ACCOUNT_PORTFOLIO_ACCOUNT,
316            creator_portfolio: Pubkey::new_unique(),
317            meta_data_url: meta_data_url.to_vec(),
318            meta_data_hash: meta_data_hash.to_vec(),
319            is_initialized: 1,
320            asset_data_len: number_assets,
321            nft_token: 1,
322            version: 2,
323            splm_stake: Pubkey::new_unique(),
324            extended_data: Pubkey::new_unique(),
325            asset_data,
326        };
327        const LEN: usize = PORTFOLIO_PREFIX + (1 * ASSET_LEN);
328        let mut packed = [0u8; LEN];
329        PackPortfolio::pack_portfolio(&portfolio, &mut packed[..]);
330        let unpacked = Portfolio::unpack_portfolio(&packed).unwrap();
331        assert_eq!(portfolio, unpacked);
332        assert_eq!(unpacked.type_account,TYPE_ACCOUNT_PORTFOLIO_ACCOUNT);
333    }
334}