solana_fund/
common.rs

1//! Common functions
2
3use {
4    crate::{fund_info::FundInfo, user_info::UserInfo},
5    solana_farm_sdk::{
6        fund::{
7            Fund, FundAssetType, FundAssets, FundCustody, FundCustodyType, FundUserRequests,
8            FundVault, FundVaultType, DISCRIMINATOR_FUND_CUSTODY, DISCRIMINATOR_FUND_VAULT,
9        },
10        id::{main_router, zero},
11        math,
12        program::{account, clock},
13        token::Token,
14        traits::Packed,
15    },
16    solana_program::{
17        account_info::AccountInfo, clock::UnixTimestamp, entrypoint::ProgramResult, msg,
18        program_error::ProgramError, pubkey::Pubkey,
19    },
20};
21
22#[allow(clippy::too_many_arguments)]
23pub fn check_wd_custody_accounts<'a, 'b>(
24    fund_program_id: &Pubkey,
25    fund_metadata: &Pubkey,
26    custody_token: &Token,
27    custody_token_metadata: &'a AccountInfo<'b>,
28    user_wd_token_account: &'a AccountInfo<'b>,
29    custody_account: &'a AccountInfo<'b>,
30    custody_fees_account: &'a AccountInfo<'b>,
31    custody_metadata: &'a AccountInfo<'b>,
32    oracle_account: &'a AccountInfo<'b>,
33) -> ProgramResult {
34    let deposit_token_mint =
35        if let Ok(mint) = account::get_token_account_mint(user_wd_token_account) {
36            mint
37        } else {
38            msg!("Error: Invalid user's deposit token account");
39            return Err(ProgramError::Custom(500));
40        };
41
42    let custody_account_mint = if let Ok(mint) = account::get_token_account_mint(custody_account) {
43        mint
44    } else {
45        msg!("Error: Invalid custody token account mint");
46        return Err(ProgramError::Custom(501));
47    };
48
49    if custody_token.mint != custody_account_mint || deposit_token_mint != custody_account_mint {
50        msg!("Error: Custody token mint mismatch");
51        return Err(ProgramError::Custom(502));
52    }
53
54    let custody = account::unpack::<FundCustody>(custody_metadata, "custody")?;
55
56    if &custody.token_ref != custody_token_metadata.key
57        || custody_token_metadata.owner != &main_router::id()
58    {
59        msg!("Error: Invalid custody token account");
60        return Err(ProgramError::Custom(503));
61    }
62
63    if custody_metadata.owner != fund_program_id
64        || custody.discriminator != DISCRIMINATOR_FUND_CUSTODY
65        || &custody.fund_ref != fund_metadata
66        || custody.custody_type != FundCustodyType::DepositWithdraw
67        || &custody.address != custody_account.key
68        || &custody.fees_address != custody_fees_account.key
69        || &custody_token.oracle_account.unwrap_or_else(zero::id) != oracle_account.key
70    {
71        msg!("Error: Invalid custody accounts");
72        Err(ProgramError::Custom(504))
73    } else {
74        Ok(())
75    }
76}
77
78#[allow(clippy::too_many_arguments)]
79pub fn check_custody_account<'a, 'b>(
80    fund_program_id: &Pubkey,
81    fund_metadata: &Pubkey,
82    custody_token: &Token,
83    custody_token_metadata: &'a AccountInfo<'b>,
84    custody_metadata: &'a AccountInfo<'b>,
85    custody_type: FundCustodyType,
86    custody_account: &'a AccountInfo<'b>,
87    custody_fees_account: Option<&Pubkey>,
88) -> ProgramResult {
89    let custody_account_mint = if let Ok(mint) = account::get_token_account_mint(custody_account) {
90        mint
91    } else {
92        msg!("Error: Invalid custody token account mint");
93        return Err(ProgramError::Custom(501));
94    };
95
96    if custody_token.mint != custody_account_mint {
97        msg!("Error: Custody token mint mismatch");
98        return Err(ProgramError::Custom(502));
99    }
100
101    let custody = account::unpack::<FundCustody>(custody_metadata, "custody")?;
102
103    if &custody.token_ref != custody_token_metadata.key
104        || custody_token_metadata.owner != &main_router::id()
105    {
106        msg!("Error: Invalid custody token account");
107        return Err(ProgramError::Custom(503));
108    }
109
110    if custody_metadata.owner != fund_program_id
111        || custody.discriminator != DISCRIMINATOR_FUND_CUSTODY
112        || &custody.fund_ref != fund_metadata
113        || custody.custody_type != custody_type
114        || &custody.address != custody_account.key
115        || &custody.fees_address != custody_fees_account.unwrap_or(&custody.fees_address)
116    {
117        msg!("Error: Invalid custody accounts");
118        return Err(ProgramError::Custom(504));
119    }
120
121    Ok(())
122}
123
124pub fn check_and_get_fund_assets_account(
125    fund: &Fund,
126    fund_assets_account: &AccountInfo,
127    assets_type: FundAssetType,
128) -> Result<FundAssets, ProgramError> {
129    let fund_assets = account::unpack::<FundAssets>(fund_assets_account, "Fund assets")?;
130
131    let fund_assets_info_derived = Pubkey::create_program_address(
132        &[
133            if assets_type == FundAssetType::Custody {
134                b"custodies_assets_info"
135            } else {
136                b"vaults_assets_info"
137            },
138            fund.name.as_bytes(),
139            &[fund_assets.bump],
140        ],
141        &fund.fund_program_id,
142    )?;
143
144    if &fund_assets_info_derived != fund_assets_account.key {
145        msg!("Error: Invalid fund assets account");
146        return Err(ProgramError::Custom(505));
147    }
148
149    Ok(fund_assets)
150}
151
152pub fn check_vault_account<'a, 'b>(
153    fund_program_id: &Pubkey,
154    fund_metadata: &'a AccountInfo<'b>,
155    vault_metadata: &'a AccountInfo<'b>,
156    vault_type: FundVaultType,
157) -> ProgramResult {
158    if vault_metadata.owner != fund_program_id {
159        msg!("Error: Invalid custody owner");
160        return Err(ProgramError::IllegalOwner);
161    }
162
163    let vault = account::unpack::<FundVault>(vault_metadata, "Vault")?;
164
165    if vault.discriminator != DISCRIMINATOR_FUND_VAULT
166        || vault.fund_ref != *fund_metadata.key
167        || vault_type != vault.vault_type
168    {
169        msg!("Error: Invalid vault metadata account");
170        return Err(ProgramError::Custom(506));
171    }
172
173    Ok(())
174}
175
176pub fn check_unpack_target_vault<'a, 'b>(
177    fund_program_id: &Pubkey,
178    router_program_id: &Pubkey,
179    fund_metadata: &Pubkey,
180    underlying_pool_id: &Pubkey,
181    fund_vault_metadata: &'a AccountInfo<'b>,
182) -> Result<FundVault, ProgramError> {
183    if fund_vault_metadata.owner != fund_program_id {
184        msg!("Error: Invalid Fund Vault metadata owner");
185        return Err(ProgramError::IllegalOwner);
186    }
187
188    let fund_vault = account::unpack::<FundVault>(fund_vault_metadata, "Fund Vault")?;
189
190    if &fund_vault.fund_ref != fund_metadata {
191        msg!("Error: Specified Vault doesn't belong to this Fund");
192        return Err(ProgramError::Custom(507));
193    }
194
195    if &fund_vault.router_program_id != router_program_id
196        || &fund_vault.underlying_pool_id != underlying_pool_id
197    {
198        msg!("Error: Invalid target Vault");
199        return Err(ProgramError::Custom(508));
200    }
201
202    Ok(fund_vault)
203}
204
205pub fn increase_vault_balance(
206    fund_vault_metadata: &AccountInfo,
207    vault: &FundVault,
208    lp_balance_increase: u64,
209) -> ProgramResult {
210    if lp_balance_increase == 0 {
211        return Ok(());
212    }
213
214    let updated_lp_balance = math::checked_add(vault.lp_balance, lp_balance_increase)?;
215    let vault_new = FundVault {
216        lp_balance: updated_lp_balance,
217        balance_update_time: clock::get_time()?,
218        ..*vault
219    };
220    vault_new.pack(*fund_vault_metadata.try_borrow_mut_data()?)?;
221
222    Ok(())
223}
224
225pub fn decrease_vault_balance(
226    fund_vault_metadata: &AccountInfo,
227    vault: &FundVault,
228    lp_balance_decrease: u64,
229) -> ProgramResult {
230    if lp_balance_decrease == 0 {
231        return Ok(());
232    }
233
234    let updated_lp_balance = math::checked_sub(vault.lp_balance, lp_balance_decrease)?;
235    let vault_new = FundVault {
236        lp_balance: updated_lp_balance,
237        balance_update_time: clock::get_time()?,
238        ..*vault
239    };
240    vault_new.pack(*fund_vault_metadata.try_borrow_mut_data()?)?;
241
242    Ok(())
243}
244
245pub fn check_user_requests_account<'a, 'b>(
246    fund: &Fund,
247    custody_token: &Token,
248    user_requests: &FundUserRequests,
249    user_account: &'a AccountInfo<'b>,
250    user_requests_account: &'a AccountInfo<'b>,
251) -> ProgramResult {
252    let user_requests_derived = Pubkey::create_program_address(
253        &[
254            b"user_requests_account",
255            custody_token.name.as_bytes(),
256            user_account.key.as_ref(),
257            fund.name.as_bytes(),
258            &[user_requests.bump],
259        ],
260        &fund.fund_program_id,
261    )?;
262
263    if user_requests_account.key != &user_requests_derived {
264        msg!("Error: Invalid user requests address");
265        Err(ProgramError::Custom(509))
266    } else {
267        Ok(())
268    }
269}
270
271pub fn check_fund_token_mint(fund: &Fund, fund_token_mint: &AccountInfo) -> ProgramResult {
272    let fund_token_mint_derived = Pubkey::create_program_address(
273        &[
274            b"fund_token_mint",
275            fund.name.as_bytes(),
276            &[fund.fund_token_bump],
277        ],
278        &fund.fund_program_id,
279    )?;
280
281    if fund_token_mint.key != &fund_token_mint_derived {
282        msg!("Error: Invalid Fund token mint");
283        Err(ProgramError::Custom(510))
284    } else {
285        Ok(())
286    }
287}
288
289pub fn check_assets_update_time(
290    assets_update_time: UnixTimestamp,
291    max_update_age_sec: u64,
292) -> ProgramResult {
293    let last_update_age_sec = math::checked_sub(clock::get_time()?, assets_update_time)?;
294    if last_update_age_sec > max_update_age_sec as i64 {
295        msg!("Error: Assets balance is stale. Contact Fund administrator.");
296        Err(ProgramError::Custom(222))
297    } else {
298        Ok(())
299    }
300}
301
302pub fn check_assets_limit_usd(
303    fund_info: &FundInfo,
304    deposit_value_usd: f64,
305) -> Result<(), ProgramError> {
306    let current_assets_usd = fund_info.get_current_assets_usd()?;
307    let assets_limit = fund_info.get_assets_limit_usd()?;
308
309    if assets_limit > 0.0 && assets_limit < deposit_value_usd + current_assets_usd {
310        let amount_left = if current_assets_usd < assets_limit {
311            assets_limit - current_assets_usd
312        } else {
313            0.0
314        };
315        msg!(
316            "Error: Fund assets limit reached ({}). Allowed max desposit USD: {}",
317            assets_limit,
318            amount_left
319        );
320        return Err(ProgramError::Custom(223));
321    }
322
323    Ok(())
324}
325
326pub fn get_fund_token_to_mint_amount(
327    current_assets_usd: f64,
328    deposit_amount: u64,
329    deposit_value_usd: f64,
330    ft_supply_amount: u64,
331) -> Result<u64, ProgramError> {
332    let ft_to_mint = if ft_supply_amount == 0 {
333        deposit_amount
334    } else if current_assets_usd <= 0.0001 {
335        msg!("Error: Assets balance is stale. Contact Fund administrator.");
336        return Err(ProgramError::Custom(222));
337    } else {
338        math::checked_as_u64(
339            math::checked_mul(
340                math::checked_as_u128(deposit_value_usd / current_assets_usd * 1000000000.0)?,
341                ft_supply_amount as u128,
342            )? / 1000000000u128,
343        )?
344    };
345
346    Ok(ft_to_mint)
347}
348
349pub fn get_fund_token_balance(
350    fund_token_account: &AccountInfo,
351    user_info: &UserInfo,
352) -> Result<u64, ProgramError> {
353    math::checked_add(
354        account::get_token_balance(fund_token_account)?,
355        user_info.get_virtual_tokens_balance()?,
356    )
357}
358
359pub fn get_fund_token_supply(
360    fund_token_mint: &AccountInfo,
361    fund_info: &FundInfo,
362) -> Result<u64, ProgramError> {
363    math::checked_add(
364        account::get_token_supply(fund_token_mint)?,
365        fund_info.get_virtual_tokens_supply()?,
366    )
367}