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 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 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 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 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 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 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 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}