spl_token_vault/
state.rs

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