mpl_token_vault/
state.rs

1use crate::utils::try_from_slice_checked;
2use borsh::{BorshDeserialize, BorshSerialize};
3use shank::ShankAccount;
4use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
5/// prefix used for PDAs to avoid certain collision attacks (https://en.wikipedia.org/wiki/Collision_attack#Chosen-prefix_collision_attack)
6pub const PREFIX: &str = "vault";
7
8#[repr(C)]
9#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq)]
10pub enum Key {
11    Uninitialized,
12    SafetyDepositBoxV1,
13    ExternalAccountKeyV1,
14    VaultV1,
15}
16
17pub const MAX_SAFETY_DEPOSIT_SIZE: usize = 1 + 32 + 32 + 32 + 1;
18pub const MAX_VAULT_SIZE: usize = 1 + 32 + 32 + 32 + 32 + 1 + 32 + 1 + 32 + 1 + 1 + 8;
19pub const MAX_EXTERNAL_ACCOUNT_SIZE: usize = 1 + 8 + 32 + 1;
20#[repr(C)]
21#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq)]
22pub enum VaultState {
23    Inactive,
24    Active,
25    Combined,
26    Deactivated,
27}
28
29#[repr(C)]
30#[derive(Clone, BorshSerialize, BorshDeserialize, ShankAccount)]
31pub struct Vault {
32    pub key: Key,
33    /// Store token program used
34    pub token_program: Pubkey,
35    /// Mint that produces the fractional shares
36    pub fraction_mint: Pubkey,
37    /// Authority who can make changes to the vault
38    pub authority: Pubkey,
39    /// treasury where fractional shares are held for redemption by authority
40    pub fraction_treasury: Pubkey,
41    /// treasury where monies are held for fractional share holders to redeem(burn) shares once buyout is made
42    pub redeem_treasury: Pubkey,
43    /// Can authority mint more shares from fraction_mint after activation
44    pub allow_further_share_creation: bool,
45
46    /// Must point at an ExternalPriceAccount, which gives permission and price for buyout.
47    pub pricing_lookup_address: Pubkey,
48    /// In inactive state, we use this to set the order key on Safety Deposit Boxes being added and
49    /// then we increment it and save so the next safety deposit box gets the next number.
50    /// In the Combined state during token redemption by authority, we use it as a decrementing counter each time
51    /// The authority of the vault withdrawals a Safety Deposit contents to count down how many
52    /// are left to be opened and closed down. Once this hits zero, and the fraction mint has zero shares,
53    /// then we can deactivate the vault.
54    pub token_type_count: u8,
55    pub state: VaultState,
56
57    /// Once combination happens, we copy price per share to vault so that if something nefarious happens
58    /// to external price account, like price change, we still have the math 'saved' for use in our calcs
59    pub locked_price_per_share: u64,
60
61    /// The [MAX_VAULT_SIZE] indicates an extra byte for a field which is actually no longer
62    /// present. Therefore we include a place holder here in order to have the struct size match
63    /// the indicated size.
64    _extra_byte: u8,
65}
66
67impl Vault {
68    pub fn from_account_info(a: &AccountInfo) -> Result<Vault, ProgramError> {
69        let vt: Vault = try_from_slice_checked(&a.data.borrow_mut(), Key::VaultV1, MAX_VAULT_SIZE)?;
70
71        Ok(vt)
72    }
73
74    pub fn get_token_type_count(a: &AccountInfo) -> u8 {
75        return a.data.borrow()[194];
76    }
77}
78
79#[repr(C)]
80#[derive(Clone, BorshSerialize, BorshDeserialize, ShankAccount)]
81pub struct SafetyDepositBox {
82    // Please note if you change this struct, be careful as we read directly off it
83    // in Metaplex to avoid serialization costs...
84    /// Each token type in a vault has it's own box that contains it's mint and a look-back
85    pub key: Key,
86    /// Key pointing to the parent vault
87    pub vault: Pubkey,
88    /// This particular token's mint
89    pub token_mint: Pubkey,
90    /// Account that stores the tokens under management
91    pub store: Pubkey,
92    /// the order in the array of registries
93    pub order: u8,
94}
95
96impl SafetyDepositBox {
97    pub fn from_account_info(a: &AccountInfo) -> Result<SafetyDepositBox, ProgramError> {
98        let sd: SafetyDepositBox = try_from_slice_checked(
99            &a.data.borrow_mut(),
100            Key::SafetyDepositBoxV1,
101            MAX_SAFETY_DEPOSIT_SIZE,
102        )?;
103
104        Ok(sd)
105    }
106
107    pub fn get_order(a: &AccountInfo) -> u8 {
108        a.data.borrow()[97]
109    }
110}
111
112#[repr(C)]
113#[derive(Clone, BorshSerialize, BorshDeserialize, ShankAccount)]
114pub struct ExternalPriceAccount {
115    pub key: Key,
116    pub price_per_share: u64,
117    /// Mint of the currency we are pricing the shares against, should be same as redeem_treasury.
118    /// Most likely will be USDC mint most of the time.
119    pub price_mint: Pubkey,
120    /// Whether or not combination has been allowed for this vault.
121    pub allowed_to_combine: bool,
122}
123
124impl ExternalPriceAccount {
125    pub fn from_account_info(a: &AccountInfo) -> Result<ExternalPriceAccount, ProgramError> {
126        let sd: ExternalPriceAccount = try_from_slice_checked(
127            &a.data.borrow_mut(),
128            Key::ExternalAccountKeyV1,
129            MAX_EXTERNAL_ACCOUNT_SIZE,
130        )?;
131
132        Ok(sd)
133    }
134}