spl_token_vault/
processor.rs

1use {
2    crate::{
3        error::VaultError,
4        instruction::VaultInstruction,
5        state::{
6            ExternalPriceAccount, Key, SafetyDepositBox, Vault, VaultState,
7            MAX_SAFETY_DEPOSIT_SIZE, PREFIX,
8        },
9        utils::{
10            assert_initialized, assert_owned_by, assert_rent_exempt, assert_token_matching,
11            assert_token_program_matches_package, assert_vault_authority_correct,
12            create_or_allocate_account_raw, spl_token_burn, spl_token_mint_to, spl_token_transfer,
13            TokenBurnParams, TokenMintToParams, TokenTransferParams,
14        },
15    },
16    borsh::{BorshDeserialize, BorshSerialize},
17    solana_program::{
18        account_info::{next_account_info, AccountInfo},
19        entrypoint::ProgramResult,
20        msg,
21        program_option::COption,
22        pubkey::Pubkey,
23        rent::Rent,
24        sysvar::Sysvar,
25    },
26    spl_token::state::{Account, Mint},
27};
28
29pub fn process_instruction(
30    program_id: &Pubkey,
31    accounts: &[AccountInfo],
32    input: &[u8],
33) -> ProgramResult {
34    let instruction = VaultInstruction::try_from_slice(input)?;
35    match instruction {
36        VaultInstruction::InitVault(args) => {
37            msg!("Instruction: Init Vault");
38            process_init_vault(program_id, accounts, args.allow_further_share_creation)
39        }
40        VaultInstruction::AddTokenToInactiveVault(args) => {
41            msg!("Instruction: Add token to vault");
42            process_add_token_to_inactivated_vault(program_id, accounts, args.amount)
43        }
44        VaultInstruction::ActivateVault(args) => {
45            msg!("Instruction: Activate Vault ");
46            process_activate_vault(program_id, accounts, args.number_of_shares)
47        }
48        VaultInstruction::CombineVault => {
49            msg!("Instruction: Combine Vault");
50            process_combine_vault(program_id, accounts)
51        }
52        VaultInstruction::RedeemShares => {
53            msg!("Instruction: Redeem Shares");
54            process_redeem_shares(program_id, accounts)
55        }
56        VaultInstruction::WithdrawTokenFromSafetyDepositBox(args) => {
57            msg!("Instruction: Withdraw Token from Safety Deposit Box");
58            process_withdraw_token_from_safety_deposit_box(program_id, accounts, args.amount)
59        }
60        VaultInstruction::MintFractionalShares(args) => {
61            msg!("Instruction: Mint new fractional shares");
62            process_mint_fractional_shares(program_id, accounts, args.number_of_shares)
63        }
64        VaultInstruction::WithdrawSharesFromTreasury(args) => {
65            msg!("Instruction: Withdraw fractional shares");
66            process_withdraw_fractional_shares_from_treasury(
67                program_id,
68                accounts,
69                args.number_of_shares,
70            )
71        }
72        VaultInstruction::AddSharesToTreasury(args) => {
73            msg!("Instruction: Add fractional shares to treasury");
74            process_add_fractional_shares_to_treasury(program_id, accounts, args.number_of_shares)
75        }
76
77        VaultInstruction::UpdateExternalPriceAccount(args) => {
78            msg!("Instruction: Update External Price Account");
79            process_update_external_price_account(
80                program_id,
81                accounts,
82                args.price_per_share,
83                args.price_mint,
84                args.allowed_to_combine,
85            )
86        }
87        VaultInstruction::SetAuthority => {
88            msg!("Instruction: Set Authority");
89            process_set_authority(program_id, accounts)
90        }
91    }
92}
93
94pub fn process_update_external_price_account(
95    _: &Pubkey,
96    accounts: &[AccountInfo],
97    price_per_share: u64,
98    price_mint: Pubkey,
99    allowed_to_combine: bool,
100) -> ProgramResult {
101    let account_info_iter = &mut accounts.iter();
102    let account = next_account_info(account_info_iter)?;
103    if !account.is_signer {
104        return Err(VaultError::ExternalPriceAccountMustBeSigner.into());
105    }
106
107    let mut external_price_account = ExternalPriceAccount::from_account_info(account)?;
108
109    external_price_account.key = Key::ExternalAccountKeyV1;
110    external_price_account.price_per_share = price_per_share;
111    external_price_account.price_mint = price_mint;
112    external_price_account.allowed_to_combine = allowed_to_combine;
113
114    external_price_account.serialize(&mut *account.data.borrow_mut())?;
115
116    Ok(())
117}
118
119pub fn process_set_authority(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
120    let account_info_iter = &mut accounts.iter();
121    let vault_info = next_account_info(account_info_iter)?;
122    let current_authority_info = next_account_info(account_info_iter)?;
123    let new_authority_info = next_account_info(account_info_iter)?;
124
125    let mut vault = Vault::from_account_info(vault_info)?;
126    assert_owned_by(vault_info, program_id)?;
127
128    if vault.authority != *current_authority_info.key {
129        return Err(VaultError::InvalidAuthority.into());
130    }
131
132    if !current_authority_info.is_signer {
133        return Err(VaultError::InvalidAuthority.into());
134    }
135
136    // Make sure new authority actually exists in some form.
137    if new_authority_info.data_is_empty() || new_authority_info.lamports() == 0 {
138        msg!("Disallowing new authority because it does not exist.");
139        return Err(VaultError::InvalidAuthority.into());
140    }
141
142    vault.authority = *new_authority_info.key;
143    vault.serialize(&mut *vault_info.data.borrow_mut())?;
144
145    Ok(())
146}
147
148pub fn process_add_fractional_shares_to_treasury(
149    program_id: &Pubkey,
150    accounts: &[AccountInfo],
151    number_of_shares: u64,
152) -> ProgramResult {
153    let account_info_iter = &mut accounts.iter();
154    let source_info = next_account_info(account_info_iter)?;
155    let fraction_treasury_info = next_account_info(account_info_iter)?;
156    let vault_info = next_account_info(account_info_iter)?;
157    let transfer_authority_info = next_account_info(account_info_iter)?;
158    let vault_authority_info = next_account_info(account_info_iter)?;
159    let token_program_info = next_account_info(account_info_iter)?;
160
161    let vault = Vault::from_account_info(vault_info)?;
162    let source: Account = assert_initialized(source_info)?;
163
164    assert_token_program_matches_package(token_program_info)?;
165    assert_owned_by(source_info, token_program_info.key)?;
166    assert_token_matching(&vault, token_program_info)?;
167    assert_owned_by(vault_info, program_id)?;
168    assert_owned_by(fraction_treasury_info, token_program_info.key)?;
169    assert_vault_authority_correct(&vault, vault_authority_info)?;
170
171    if vault.state != VaultState::Active {
172        return Err(VaultError::VaultShouldBeActive.into());
173    }
174
175    if *fraction_treasury_info.key != vault.fraction_treasury {
176        return Err(VaultError::FractionTreasuryNeedsToMatchVault.into());
177    }
178
179    if source.mint != vault.fraction_mint {
180        return Err(VaultError::SourceAccountNeedsToMatchFractionMint.into());
181    }
182
183    if source.amount < number_of_shares {
184        return Err(VaultError::NotEnoughShares.into());
185    }
186
187    let (_, bump_seed) = Pubkey::find_program_address(
188        &[
189            PREFIX.as_bytes(),
190            program_id.as_ref(),
191            vault_info.key.as_ref(),
192        ],
193        program_id,
194    );
195    let authority_signer_seeds = &[
196        PREFIX.as_bytes(),
197        program_id.as_ref(),
198        vault_info.key.as_ref(),
199        &[bump_seed],
200    ];
201
202    spl_token_transfer(TokenTransferParams {
203        source: source_info.clone(),
204        destination: fraction_treasury_info.clone(),
205        amount: number_of_shares,
206        authority: transfer_authority_info.clone(),
207        authority_signer_seeds,
208        token_program: token_program_info.clone(),
209    })?;
210
211    Ok(())
212}
213
214pub fn process_withdraw_fractional_shares_from_treasury(
215    program_id: &Pubkey,
216    accounts: &[AccountInfo],
217    number_of_shares: u64,
218) -> ProgramResult {
219    let account_info_iter = &mut accounts.iter();
220    let destination_info = next_account_info(account_info_iter)?;
221    let fraction_treasury_info = next_account_info(account_info_iter)?;
222    let vault_info = next_account_info(account_info_iter)?;
223    let transfer_authority_info = next_account_info(account_info_iter)?;
224    let vault_authority_info = next_account_info(account_info_iter)?;
225    let token_program_info = next_account_info(account_info_iter)?;
226    let rent_info = next_account_info(account_info_iter)?;
227
228    let rent = &Rent::from_account_info(rent_info)?;
229    let vault = Vault::from_account_info(vault_info)?;
230    let destination: Account = assert_initialized(destination_info)?;
231    let fraction_treasury: Account = assert_initialized(fraction_treasury_info)?;
232
233    // We watch out for you!
234    assert_token_program_matches_package(token_program_info)?;
235    assert_rent_exempt(rent, destination_info)?;
236    assert_owned_by(destination_info, token_program_info.key)?;
237    assert_token_matching(&vault, token_program_info)?;
238    assert_owned_by(vault_info, program_id)?;
239    assert_vault_authority_correct(&vault, vault_authority_info)?;
240    assert_owned_by(fraction_treasury_info, token_program_info.key)?;
241
242    if vault.state != VaultState::Active {
243        return Err(VaultError::VaultShouldBeActive.into());
244    }
245
246    if *fraction_treasury_info.key != vault.fraction_treasury {
247        return Err(VaultError::FractionTreasuryNeedsToMatchVault.into());
248    }
249
250    if destination.mint != vault.fraction_mint {
251        return Err(VaultError::DestinationAccountNeedsToMatchFractionMint.into());
252    }
253
254    if fraction_treasury.amount < number_of_shares {
255        return Err(VaultError::NotEnoughShares.into());
256    }
257
258    let (authority, bump_seed) = Pubkey::find_program_address(
259        &[
260            PREFIX.as_bytes(),
261            program_id.as_ref(),
262            vault_info.key.as_ref(),
263        ],
264        program_id,
265    );
266    let authority_signer_seeds = &[
267        PREFIX.as_bytes(),
268        program_id.as_ref(),
269        vault_info.key.as_ref(),
270        &[bump_seed],
271    ];
272
273    if authority != *transfer_authority_info.key {
274        return Err(VaultError::InvalidAuthority.into());
275    }
276
277    spl_token_transfer(TokenTransferParams {
278        source: fraction_treasury_info.clone(),
279        destination: destination_info.clone(),
280        amount: number_of_shares,
281        authority: transfer_authority_info.clone(),
282        authority_signer_seeds,
283        token_program: token_program_info.clone(),
284    })?;
285
286    Ok(())
287}
288
289pub fn process_mint_fractional_shares(
290    program_id: &Pubkey,
291    accounts: &[AccountInfo],
292    number_of_shares: u64,
293) -> ProgramResult {
294    let account_info_iter = &mut accounts.iter();
295    let fraction_treasury_info = next_account_info(account_info_iter)?;
296    let fraction_mint_info = next_account_info(account_info_iter)?;
297    let vault_info = next_account_info(account_info_iter)?;
298    let mint_authority_info = next_account_info(account_info_iter)?;
299    let vault_authority_info = next_account_info(account_info_iter)?;
300    let token_program_info = next_account_info(account_info_iter)?;
301
302    let vault = Vault::from_account_info(vault_info)?;
303
304    assert_token_program_matches_package(token_program_info)?;
305    assert_token_matching(&vault, token_program_info)?;
306    assert_owned_by(vault_info, program_id)?;
307    assert_owned_by(fraction_mint_info, &token_program_info.key)?;
308    assert_owned_by(fraction_treasury_info, &token_program_info.key)?;
309    assert_vault_authority_correct(&vault, vault_authority_info)?;
310
311    if vault.state != VaultState::Active {
312        return Err(VaultError::VaultShouldBeActive.into());
313    }
314
315    if *fraction_treasury_info.key != vault.fraction_treasury {
316        return Err(VaultError::FractionTreasuryNeedsToMatchVault.into());
317    }
318
319    if fraction_mint_info.key != &vault.fraction_mint {
320        return Err(VaultError::VaultMintNeedsToMatchVault.into());
321    }
322
323    if !vault.allow_further_share_creation {
324        return Err(VaultError::VaultDoesNotAllowNewShareMinting.into());
325    }
326
327    let (authority, bump_seed) = Pubkey::find_program_address(
328        &[
329            PREFIX.as_bytes(),
330            program_id.as_ref(),
331            vault_info.key.as_ref(),
332        ],
333        program_id,
334    );
335    let authority_signer_seeds = &[
336        PREFIX.as_bytes(),
337        program_id.as_ref(),
338        vault_info.key.as_ref(),
339        &[bump_seed],
340    ];
341
342    if authority != *mint_authority_info.key {
343        return Err(VaultError::InvalidAuthority.into());
344    }
345
346    spl_token_mint_to(TokenMintToParams {
347        mint: fraction_mint_info.clone(),
348        destination: fraction_treasury_info.clone(),
349        amount: number_of_shares,
350        authority: mint_authority_info.clone(),
351        authority_signer_seeds,
352        token_program: token_program_info.clone(),
353    })?;
354
355    Ok(())
356}
357
358pub fn process_withdraw_token_from_safety_deposit_box(
359    program_id: &Pubkey,
360    accounts: &[AccountInfo],
361    amount: u64,
362) -> ProgramResult {
363    let account_info_iter = &mut accounts.iter();
364    let destination_info = next_account_info(account_info_iter)?;
365    let safety_deposit_info = next_account_info(account_info_iter)?;
366    let store_info = next_account_info(account_info_iter)?;
367    let vault_info = next_account_info(account_info_iter)?;
368    let fraction_mint_info = next_account_info(account_info_iter)?;
369    let vault_authority_info = next_account_info(account_info_iter)?;
370    let transfer_authority_info = next_account_info(account_info_iter)?;
371    let token_program_info = next_account_info(account_info_iter)?;
372    let rent_info = next_account_info(account_info_iter)?;
373
374    let rent = &Rent::from_account_info(rent_info)?;
375    let mut vault = Vault::from_account_info(vault_info)?;
376    let safety_deposit = SafetyDepositBox::from_account_info(safety_deposit_info)?;
377    let fraction_mint: Mint = assert_initialized(fraction_mint_info)?;
378    let destination: Account = assert_initialized(destination_info)?;
379    let store: Account = assert_initialized(store_info)?;
380
381    // We watch out for you!
382    assert_token_program_matches_package(token_program_info)?;
383    assert_rent_exempt(rent, destination_info)?;
384    assert_owned_by(destination_info, token_program_info.key)?;
385    assert_owned_by(safety_deposit_info, program_id)?;
386    assert_owned_by(store_info, token_program_info.key)?;
387    assert_owned_by(vault_info, program_id)?;
388    assert_owned_by(fraction_mint_info, token_program_info.key)?;
389
390    assert_token_matching(&vault, token_program_info)?;
391    assert_vault_authority_correct(&vault, vault_authority_info)?;
392
393    if vault.state != VaultState::Combined {
394        // if we allow withdrawals in inactive state, could possibly have two safety deposits with the same
395        // order key. Instead require user to take vault through combined -> deactivated cycle and restart
396        // if they make mistake.
397        return Err(VaultError::VaultShouldBeCombined.into());
398    }
399
400    if safety_deposit.vault != *vault_info.key {
401        return Err(VaultError::SafetyDepositBoxVaultMismatch.into());
402    }
403
404    if fraction_mint_info.key != &vault.fraction_mint {
405        return Err(VaultError::VaultMintNeedsToMatchVault.into());
406    }
407
408    if *store_info.key != safety_deposit.store {
409        return Err(VaultError::StoreDoesNotMatchSafetyDepositBox.into());
410    }
411
412    if store.amount == 0 {
413        return Err(VaultError::StoreEmpty.into());
414    }
415
416    if store.amount < amount {
417        return Err(VaultError::StoreLessThanAmount.into());
418    }
419
420    if destination.mint != safety_deposit.token_mint {
421        return Err(VaultError::DestinationAccountNeedsToMatchTokenMint.into());
422    }
423
424    let (authority, bump_seed) = Pubkey::find_program_address(
425        &[
426            PREFIX.as_bytes(),
427            program_id.as_ref(),
428            vault_info.key.as_ref(),
429        ],
430        program_id,
431    );
432    let authority_signer_seeds = &[
433        PREFIX.as_bytes(),
434        program_id.as_ref(),
435        vault_info.key.as_ref(),
436        &[bump_seed],
437    ];
438
439    if authority != *transfer_authority_info.key {
440        return Err(VaultError::InvalidAuthority.into());
441    }
442
443    spl_token_transfer(TokenTransferParams {
444        source: store_info.clone(),
445        destination: destination_info.clone(),
446        amount,
447        authority: transfer_authority_info.clone(),
448        authority_signer_seeds,
449        token_program: token_program_info.clone(),
450    })?;
451
452    match store.amount.checked_sub(amount) {
453        Some(val) => {
454            if val == 0 {
455                vault.token_type_count = match vault.token_type_count.checked_sub(1) {
456                    Some(val) => val,
457                    None => return Err(VaultError::NumericalOverflowError.into()),
458                };
459
460                if fraction_mint.supply == 0 && vault.token_type_count == 0 {
461                    vault.state = VaultState::Deactivated;
462                    vault.serialize(&mut *vault_info.data.borrow_mut())?;
463                }
464            }
465        }
466        None => return Err(VaultError::NumericalOverflowError.into()),
467    };
468
469    Ok(())
470}
471
472pub fn process_redeem_shares(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
473    let account_info_iter = &mut accounts.iter();
474
475    let outstanding_shares_info = next_account_info(account_info_iter)?;
476    let destination_info = next_account_info(account_info_iter)?;
477    let fraction_mint_info = next_account_info(account_info_iter)?;
478    let redeem_treasury_info = next_account_info(account_info_iter)?;
479    let transfer_authority_info = next_account_info(account_info_iter)?;
480    let burn_authority_info = next_account_info(account_info_iter)?;
481    let vault_info = next_account_info(account_info_iter)?;
482    let token_program_info = next_account_info(account_info_iter)?;
483    let rent_info = next_account_info(account_info_iter)?;
484
485    let rent = &Rent::from_account_info(rent_info)?;
486    let mut vault = Vault::from_account_info(vault_info)?;
487    let fraction_mint: Mint = assert_initialized(fraction_mint_info)?;
488    let outstanding_shares: Account = assert_initialized(outstanding_shares_info)?;
489    let destination: Account = assert_initialized(destination_info)?;
490    let redeem_treasury: Account = assert_initialized(redeem_treasury_info)?;
491    // We watch out for you!
492
493    assert_token_program_matches_package(token_program_info)?;
494    assert_rent_exempt(rent, destination_info)?;
495    assert_owned_by(destination_info, token_program_info.key)?;
496    assert_owned_by(vault_info, program_id)?;
497    assert_owned_by(outstanding_shares_info, token_program_info.key)?;
498    assert_owned_by(fraction_mint_info, token_program_info.key)?;
499    assert_owned_by(redeem_treasury_info, token_program_info.key)?;
500    assert_token_matching(&vault, token_program_info)?;
501
502    if outstanding_shares.amount == 0 {
503        return Err(VaultError::NoShares.into());
504    }
505
506    if outstanding_shares.mint != *fraction_mint_info.key {
507        return Err(VaultError::OutstandingShareAccountNeedsToMatchFractionalMint.into());
508    }
509
510    if destination.mint != redeem_treasury.mint {
511        return Err(VaultError::DestinationAccountNeedsToMatchRedeemMint.into());
512    }
513
514    if vault.state != VaultState::Combined {
515        return Err(VaultError::VaultShouldBeCombined.into());
516    }
517
518    if fraction_mint_info.key != &vault.fraction_mint {
519        return Err(VaultError::VaultMintNeedsToMatchVault.into());
520    }
521
522    if redeem_treasury_info.key != &vault.redeem_treasury {
523        return Err(VaultError::RedeemTreasuryNeedsToMatchVault.into());
524    }
525
526    if fraction_mint.supply == 0 {
527        // Basically impossible but I want to be safe
528        return Err(VaultError::FractionSupplyEmpty.into());
529    }
530
531    let we_owe_you = match vault
532        .locked_price_per_share
533        .checked_mul(outstanding_shares.amount)
534    {
535        Some(val) => val,
536        None => return Err(VaultError::NumericalOverflowError.into()),
537    };
538
539    let (_, bump_seed) = Pubkey::find_program_address(
540        &[
541            PREFIX.as_bytes(),
542            program_id.as_ref(),
543            vault_info.key.as_ref(),
544        ],
545        program_id,
546    );
547    let authority_signer_seeds = &[
548        PREFIX.as_bytes(),
549        program_id.as_ref(),
550        vault_info.key.as_ref(),
551        &[bump_seed],
552    ];
553
554    spl_token_transfer(TokenTransferParams {
555        source: redeem_treasury_info.clone(),
556        destination: destination_info.clone(),
557        amount: we_owe_you,
558        authority: transfer_authority_info.clone(),
559        authority_signer_seeds,
560        token_program: token_program_info.clone(),
561    })?;
562
563    spl_token_burn(TokenBurnParams {
564        mint: fraction_mint_info.clone(),
565        amount: outstanding_shares.amount,
566        authority: burn_authority_info.clone(),
567        authority_signer_seeds,
568        token_program: token_program_info.clone(),
569        source: outstanding_shares_info.clone(),
570    })?;
571
572    let fractional_remaining = match fraction_mint.supply.checked_sub(outstanding_shares.amount) {
573        Some(val) => val,
574        None => return Err(VaultError::NumericalOverflowError.into()),
575    };
576
577    if fractional_remaining == 0 && vault.token_type_count == 0 {
578        vault.state = VaultState::Deactivated;
579        vault.serialize(&mut *vault_info.data.borrow_mut())?;
580    }
581
582    Ok(())
583}
584
585pub fn process_combine_vault(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
586    let account_info_iter = &mut accounts.iter();
587
588    let vault_info = next_account_info(account_info_iter)?;
589    let your_outstanding_shares_info = next_account_info(account_info_iter)?;
590    let your_payment_info = next_account_info(account_info_iter)?;
591    let fraction_mint_info = next_account_info(account_info_iter)?;
592    let fraction_treasury_info = next_account_info(account_info_iter)?;
593    let redeem_treasury_info = next_account_info(account_info_iter)?;
594    let new_vault_authority_info = next_account_info(account_info_iter)?;
595    let vault_authority_info = next_account_info(account_info_iter)?;
596    let transfer_authority_info = next_account_info(account_info_iter)?;
597    let fraction_burn_authority_info = next_account_info(account_info_iter)?;
598    let external_pricing_info = next_account_info(account_info_iter)?;
599    let token_program_info = next_account_info(account_info_iter)?;
600
601    let mut vault = Vault::from_account_info(vault_info)?;
602    let fraction_mint: Mint = assert_initialized(fraction_mint_info)?;
603    let fraction_treasury: Account = assert_initialized(fraction_treasury_info)?;
604    let redeem_treasury: Account = assert_initialized(redeem_treasury_info)?;
605    let your_payment_account: Account = assert_initialized(your_payment_info)?;
606    let your_outstanding_shares: Account = assert_initialized(your_outstanding_shares_info)?;
607    let external_pricing = ExternalPriceAccount::from_account_info(external_pricing_info)?;
608
609    assert_token_program_matches_package(token_program_info)?;
610    assert_token_matching(&vault, token_program_info)?;
611    assert_owned_by(vault_info, program_id)?;
612    assert_owned_by(your_outstanding_shares_info, token_program_info.key)?;
613    assert_owned_by(your_payment_info, token_program_info.key)?;
614    assert_owned_by(fraction_mint_info, token_program_info.key)?;
615    assert_owned_by(fraction_treasury_info, token_program_info.key)?;
616    assert_owned_by(redeem_treasury_info, token_program_info.key)?;
617
618    assert_vault_authority_correct(&vault, vault_authority_info)?;
619
620    if vault.state != VaultState::Active {
621        return Err(VaultError::VaultShouldBeActive.into());
622    }
623
624    if your_payment_account.mint != external_pricing.price_mint {
625        return Err(VaultError::PaymentMintShouldMatchPricingMint.into());
626    }
627
628    if redeem_treasury.mint != external_pricing.price_mint {
629        // Did someone mess with our oracle?
630        return Err(VaultError::RedeemTreasuryMintShouldMatchPricingMint.into());
631    }
632
633    if your_outstanding_shares.mint != *fraction_mint_info.key {
634        return Err(VaultError::ShareMintShouldMatchFractionalMint.into());
635    }
636
637    if fraction_mint_info.key != &vault.fraction_mint {
638        return Err(VaultError::VaultMintNeedsToMatchVault.into());
639    }
640
641    if redeem_treasury_info.key != &vault.redeem_treasury {
642        return Err(VaultError::RedeemTreasuryNeedsToMatchVault.into());
643    }
644
645    if !external_pricing.allowed_to_combine {
646        return Err(VaultError::NotAllowedToCombine.into());
647    }
648
649    let total_market_cap = match fraction_mint
650        .supply
651        .checked_mul(external_pricing.price_per_share)
652    {
653        Some(val) => val,
654        None => return Err(VaultError::NumericalOverflowError.into()),
655    };
656
657    let stored_market_cap = match fraction_treasury
658        .amount
659        .checked_mul(external_pricing.price_per_share)
660    {
661        Some(val) => val,
662        None => return Err(VaultError::NumericalOverflowError.into()),
663    };
664
665    let circulating_market_cap = match total_market_cap.checked_sub(stored_market_cap) {
666        Some(val) => val,
667        None => return Err(VaultError::NumericalOverflowError.into()),
668    };
669
670    let your_share_value = match your_outstanding_shares
671        .amount
672        .checked_mul(external_pricing.price_per_share)
673    {
674        Some(val) => val,
675        None => return Err(VaultError::NumericalOverflowError.into()),
676    };
677
678    let what_you_owe = match circulating_market_cap.checked_sub(your_share_value) {
679        Some(val) => val,
680        None => return Err(VaultError::NumericalOverflowError.into()),
681    };
682
683    if your_payment_account.amount < what_you_owe {
684        return Err(VaultError::CannotAffordToCombineThisVault.into());
685    }
686
687    let (authority, bump_seed) = Pubkey::find_program_address(
688        &[
689            PREFIX.as_bytes(),
690            program_id.as_ref(),
691            vault_info.key.as_ref(),
692        ],
693        program_id,
694    );
695    let authority_signer_seeds = &[
696        PREFIX.as_bytes(),
697        program_id.as_ref(),
698        vault_info.key.as_ref(),
699        &[bump_seed],
700    ];
701
702    if authority != *fraction_burn_authority_info.key {
703        return Err(VaultError::InvalidAuthority.into());
704    }
705
706    spl_token_transfer(TokenTransferParams {
707        source: your_payment_info.clone(),
708        destination: redeem_treasury_info.clone(),
709        amount: what_you_owe,
710        authority: transfer_authority_info.clone(),
711        authority_signer_seeds,
712        token_program: token_program_info.clone(),
713    })?;
714
715    spl_token_burn(TokenBurnParams {
716        mint: fraction_mint_info.clone(),
717        amount: your_outstanding_shares.amount,
718        authority: transfer_authority_info.clone(),
719        authority_signer_seeds,
720        token_program: token_program_info.clone(),
721        source: your_outstanding_shares_info.clone(),
722    })?;
723
724    spl_token_burn(TokenBurnParams {
725        mint: fraction_mint_info.clone(),
726        amount: fraction_treasury.amount,
727        authority: fraction_burn_authority_info.clone(),
728        authority_signer_seeds,
729        token_program: token_program_info.clone(),
730        source: fraction_treasury_info.clone(),
731    })?;
732
733    vault.state = VaultState::Combined;
734    vault.authority = *new_vault_authority_info.key;
735    vault.locked_price_per_share = external_pricing.price_per_share;
736    vault.serialize(&mut *vault_info.data.borrow_mut())?;
737
738    Ok(())
739}
740
741pub fn process_activate_vault(
742    program_id: &Pubkey,
743    accounts: &[AccountInfo],
744    number_of_shares: u64,
745) -> ProgramResult {
746    let account_info_iter = &mut accounts.iter();
747
748    let vault_info = next_account_info(account_info_iter)?;
749    let fraction_mint_info = next_account_info(account_info_iter)?;
750    let fraction_treasury_info = next_account_info(account_info_iter)?;
751    let fractional_mint_authority_info = next_account_info(account_info_iter)?;
752    let vault_authority_info = next_account_info(account_info_iter)?;
753    let token_program_info = next_account_info(account_info_iter)?;
754
755    let mut vault = Vault::from_account_info(vault_info)?;
756    assert_token_program_matches_package(token_program_info)?;
757    assert_owned_by(vault_info, program_id)?;
758    assert_owned_by(fraction_mint_info, token_program_info.key)?;
759    assert_owned_by(fraction_treasury_info, token_program_info.key)?;
760    assert_token_matching(&vault, token_program_info)?;
761    assert_vault_authority_correct(&vault, vault_authority_info)?;
762
763    if vault.state != VaultState::Inactive {
764        return Err(VaultError::VaultShouldBeInactive.into());
765    }
766
767    let (authority_key, bump_seed) = Pubkey::find_program_address(
768        &[
769            PREFIX.as_bytes(),
770            program_id.as_ref(),
771            vault_info.key.as_ref(),
772        ],
773        program_id,
774    );
775    if fractional_mint_authority_info.key != &authority_key {
776        return Err(VaultError::InvalidAuthority.into());
777    }
778    let authority_signer_seeds = &[
779        PREFIX.as_bytes(),
780        program_id.as_ref(),
781        vault_info.key.as_ref(),
782        &[bump_seed],
783    ];
784
785    spl_token_mint_to(TokenMintToParams {
786        mint: fraction_mint_info.clone(),
787        destination: fraction_treasury_info.clone(),
788        amount: number_of_shares,
789        authority: fractional_mint_authority_info.clone(),
790        authority_signer_seeds,
791        token_program: token_program_info.clone(),
792    })?;
793
794    vault.state = VaultState::Active;
795    vault.serialize(&mut *vault_info.data.borrow_mut())?;
796
797    Ok(())
798}
799
800pub fn process_add_token_to_inactivated_vault(
801    program_id: &Pubkey,
802    accounts: &[AccountInfo],
803    amount: u64,
804) -> ProgramResult {
805    let account_info_iter = &mut accounts.iter();
806    let safety_deposit_account_info = next_account_info(account_info_iter)?;
807    let token_account_info = next_account_info(account_info_iter)?;
808    let store_info = next_account_info(account_info_iter)?;
809    let vault_info = next_account_info(account_info_iter)?;
810    let vault_authority_info = next_account_info(account_info_iter)?;
811    let payer_info = next_account_info(account_info_iter)?;
812    let transfer_authority_info = next_account_info(account_info_iter)?;
813    let token_program_info = next_account_info(account_info_iter)?;
814    let rent_info = next_account_info(account_info_iter)?;
815    let system_account_info = next_account_info(account_info_iter)?;
816
817    let rent = &Rent::from_account_info(rent_info)?;
818    assert_token_program_matches_package(token_program_info)?;
819    assert_owned_by(vault_info, program_id)?;
820    assert_rent_exempt(rent, token_account_info)?;
821    assert_rent_exempt(rent, vault_info)?;
822    assert_owned_by(store_info, token_program_info.key)?;
823    assert_owned_by(token_account_info, token_program_info.key)?;
824    if !safety_deposit_account_info.data_is_empty() {
825        return Err(VaultError::AlreadyInitialized.into());
826    }
827
828    let token_account: Account = assert_initialized(token_account_info)?;
829    let store: Account = assert_initialized(store_info)?;
830    let mut vault = Vault::from_account_info(vault_info)?;
831    assert_token_matching(&vault, token_program_info)?;
832    assert_vault_authority_correct(&vault, vault_authority_info)?;
833
834    if vault.state != VaultState::Inactive {
835        return Err(VaultError::VaultShouldBeInactive.into());
836    }
837
838    if token_account.amount == 0 {
839        return Err(VaultError::TokenAccountContainsNoTokens.into());
840    }
841
842    if token_account.amount < amount {
843        return Err(VaultError::TokenAccountAmountLessThanAmountSpecified.into());
844    }
845
846    if store.amount > 0 {
847        return Err(VaultError::VaultAccountIsNotEmpty.into());
848    }
849
850    let seeds = &[
851        PREFIX.as_bytes(),
852        &program_id.as_ref(),
853        vault_info.key.as_ref(),
854    ];
855    let (authority, _) = Pubkey::find_program_address(seeds, program_id);
856
857    if store.owner != authority {
858        return Err(VaultError::VaultAccountIsNotOwnedByProgram.into());
859    }
860
861    if store.delegate != COption::None {
862        return Err(VaultError::DelegateShouldBeNone.into());
863    }
864
865    if store.close_authority != COption::None {
866        return Err(VaultError::CloseAuthorityShouldBeNone.into());
867    }
868
869    let seeds = &[
870        PREFIX.as_bytes(),
871        vault_info.key.as_ref(),
872        token_account.mint.as_ref(),
873    ];
874    let (safety_deposit_account_key, bump_seed) = Pubkey::find_program_address(seeds, program_id);
875
876    if safety_deposit_account_key != *safety_deposit_account_info.key {
877        return Err(VaultError::SafetyDepositAddressInvalid.into());
878    }
879    let authority_signer_seeds = &[
880        PREFIX.as_bytes(),
881        vault_info.key.as_ref(),
882        token_account.mint.as_ref(),
883        &[bump_seed],
884    ];
885    create_or_allocate_account_raw(
886        *program_id,
887        safety_deposit_account_info,
888        rent_info,
889        system_account_info,
890        payer_info,
891        MAX_SAFETY_DEPOSIT_SIZE,
892        authority_signer_seeds,
893    )?;
894
895    let mut safety_deposit_account =
896        SafetyDepositBox::from_account_info(safety_deposit_account_info)?;
897    safety_deposit_account.key = Key::SafetyDepositBoxV1;
898    safety_deposit_account.vault = *vault_info.key;
899    safety_deposit_account.token_mint = token_account.mint;
900    safety_deposit_account.store = *store_info.key;
901    safety_deposit_account.order = vault.token_type_count;
902
903    safety_deposit_account.serialize(&mut *safety_deposit_account_info.data.borrow_mut())?;
904
905    vault.token_type_count = match vault.token_type_count.checked_add(1) {
906        Some(val) => val,
907        None => return Err(VaultError::NumericalOverflowError.into()),
908    };
909
910    vault.serialize(&mut *vault_info.data.borrow_mut())?;
911
912    spl_token_transfer(TokenTransferParams {
913        source: token_account_info.clone(),
914        destination: store_info.clone(),
915        amount,
916        authority: transfer_authority_info.clone(),
917        authority_signer_seeds,
918        token_program: token_program_info.clone(),
919    })?;
920
921    Ok(())
922}
923
924pub fn process_init_vault(
925    program_id: &Pubkey,
926    accounts: &[AccountInfo],
927    allow_further_share_creation: bool,
928) -> ProgramResult {
929    let account_info_iter = &mut accounts.iter();
930    let fraction_mint_info = next_account_info(account_info_iter)?;
931    let redeem_treasury_info = next_account_info(account_info_iter)?;
932    let fraction_treasury_info = next_account_info(account_info_iter)?;
933    let vault_info = next_account_info(account_info_iter)?;
934    let authority_info = next_account_info(account_info_iter)?;
935    let pricing_lookup_address = next_account_info(account_info_iter)?;
936    let token_program_info = next_account_info(account_info_iter)?;
937    let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
938
939    let fraction_mint: Mint = assert_initialized(fraction_mint_info)?;
940    let redeem_treasury: Account = assert_initialized(redeem_treasury_info)?;
941    let fraction_treasury: Account = assert_initialized(fraction_treasury_info)?;
942    let mut vault = Vault::from_account_info(vault_info)?;
943
944    if vault.key != Key::Uninitialized {
945        return Err(VaultError::AlreadyInitialized.into());
946    }
947
948    let external_pricing_lookup = ExternalPriceAccount::from_account_info(pricing_lookup_address)?;
949
950    assert_token_program_matches_package(token_program_info)?;
951    assert_rent_exempt(rent, redeem_treasury_info)?;
952    assert_rent_exempt(rent, fraction_treasury_info)?;
953    assert_rent_exempt(rent, fraction_mint_info)?;
954    assert_rent_exempt(rent, vault_info)?;
955    assert_rent_exempt(rent, pricing_lookup_address)?;
956    assert_owned_by(fraction_mint_info, token_program_info.key)?;
957    assert_owned_by(fraction_treasury_info, token_program_info.key)?;
958    assert_owned_by(redeem_treasury_info, token_program_info.key)?;
959    assert_owned_by(vault_info, program_id)?;
960
961    if fraction_mint.supply != 0 {
962        return Err(VaultError::VaultMintNotEmpty.into());
963    }
964
965    let seeds = &[
966        PREFIX.as_bytes(),
967        &program_id.as_ref(),
968        vault_info.key.as_ref(),
969    ];
970    let (authority, _) = Pubkey::find_program_address(seeds, &program_id);
971
972    match fraction_mint.mint_authority {
973        solana_program::program_option::COption::None => {
974            return Err(VaultError::VaultAuthorityNotProgram.into());
975        }
976        solana_program::program_option::COption::Some(val) => {
977            if val != authority {
978                return Err(VaultError::VaultAuthorityNotProgram.into());
979            }
980        }
981    }
982    match fraction_mint.freeze_authority {
983        solana_program::program_option::COption::None => {
984            return Err(VaultError::VaultAuthorityNotProgram.into());
985        }
986        solana_program::program_option::COption::Some(val) => {
987            if val != authority {
988                return Err(VaultError::VaultAuthorityNotProgram.into());
989            }
990        }
991    }
992
993    if redeem_treasury.amount != 0 {
994        return Err(VaultError::TreasuryNotEmpty.into());
995    }
996
997    if redeem_treasury.owner != authority {
998        return Err(VaultError::TreasuryOwnerNotProgram.into());
999    }
1000
1001    if redeem_treasury.delegate != COption::None {
1002        return Err(VaultError::DelegateShouldBeNone.into());
1003    }
1004
1005    if redeem_treasury.close_authority != COption::None {
1006        return Err(VaultError::CloseAuthorityShouldBeNone.into());
1007    }
1008
1009    if redeem_treasury.mint != external_pricing_lookup.price_mint {
1010        return Err(VaultError::RedeemTreasuryMintMustMatchLookupMint.into());
1011    }
1012
1013    if redeem_treasury.mint == *fraction_mint_info.key {
1014        return Err(VaultError::RedeemTreasuryCantShareSameMintAsFraction.into());
1015    }
1016
1017    if fraction_treasury.amount != 0 {
1018        return Err(VaultError::TreasuryNotEmpty.into());
1019    }
1020
1021    if fraction_treasury.owner != authority {
1022        return Err(VaultError::TreasuryOwnerNotProgram.into());
1023    }
1024
1025    if fraction_treasury.delegate != COption::None {
1026        return Err(VaultError::DelegateShouldBeNone.into());
1027    }
1028
1029    if fraction_treasury.close_authority != COption::None {
1030        return Err(VaultError::CloseAuthorityShouldBeNone.into());
1031    }
1032
1033    if fraction_treasury.mint != *fraction_mint_info.key {
1034        return Err(VaultError::VaultTreasuryMintDoesNotMatchVaultMint.into());
1035    }
1036
1037    vault.key = Key::VaultV1;
1038    vault.token_program = *token_program_info.key;
1039    vault.redeem_treasury = *redeem_treasury_info.key;
1040    vault.fraction_treasury = *fraction_treasury_info.key;
1041    vault.fraction_mint = *fraction_mint_info.key;
1042    vault.pricing_lookup_address = *pricing_lookup_address.key;
1043    vault.allow_further_share_creation = allow_further_share_creation;
1044    vault.authority = *authority_info.key;
1045    vault.token_type_count = 0;
1046    vault.state = VaultState::Inactive;
1047
1048    vault.serialize(&mut *vault_info.data.borrow_mut())?;
1049
1050    Ok(())
1051}