1use std::collections::BTreeSet;
2
3use anchor_lang::prelude::*;
4use anchor_spl::{
5 associated_token::AssociatedToken,
6 token_2022::{transfer_checked, TransferChecked},
7 token_interface::{Mint, TokenAccount, TokenInterface},
8};
9use gmsol_store::{
10 cpi::{
11 accounts::{ClearAllPrices, ConfirmGtExchangeVault, SetPricesFromPriceFeed},
12 clear_all_prices, confirm_gt_exchange_vault, set_prices_from_price_feed,
13 },
14 program::GmsolStore,
15 states::{gt::GtExchangeVault, Chainlink, Oracle, Seed, Store},
16 utils::{token::is_associated_token_account_with_program_id, CpiAuthentication, WithStore},
17 CoreError,
18};
19use gmsol_utils::InitSpace;
20
21use crate::{
22 constants,
23 states::{
24 config::{Config, ReceiverSigner},
25 treasury::TreasuryVaultConfig,
26 GtBank,
27 },
28};
29
30#[derive(Accounts)]
32#[instruction(index: u16)]
33pub struct InitializeTreasuryVaultConfig<'info> {
34 #[account(mut)]
36 pub authority: Signer<'info>,
37 pub store: UncheckedAccount<'info>,
40 #[account(has_one = store)]
42 pub config: AccountLoader<'info, Config>,
43 #[account(
45 init,
46 payer = authority,
47 space = 8 + TreasuryVaultConfig::INIT_SPACE,
48 seeds = [TreasuryVaultConfig::SEED, config.key().as_ref(), &index.to_le_bytes()],
49 bump,
50 )]
51 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
52 pub store_program: Program<'info, GmsolStore>,
54 pub system_program: Program<'info, System>,
56}
57
58pub(crate) fn unchecked_initialize_treasury_vault_config(
62 ctx: Context<InitializeTreasuryVaultConfig>,
63 index: u16,
64) -> Result<()> {
65 ctx.accounts.treasury_vault_config.load_init()?.init(
66 ctx.bumps.treasury_vault_config,
67 index,
68 &ctx.accounts.config.key(),
69 );
70 Ok(())
71}
72
73impl<'info> WithStore<'info> for InitializeTreasuryVaultConfig<'info> {
74 fn store_program(&self) -> AccountInfo<'info> {
75 self.store_program.to_account_info()
76 }
77
78 fn store(&self) -> AccountInfo<'info> {
79 self.store.to_account_info()
80 }
81}
82
83impl<'info> CpiAuthentication<'info> for InitializeTreasuryVaultConfig<'info> {
84 fn authority(&self) -> AccountInfo<'info> {
85 self.authority.to_account_info()
86 }
87
88 fn on_error(&self) -> Result<()> {
89 err!(CoreError::PermissionDenied)
90 }
91}
92
93#[derive(Accounts)]
95pub struct InsertTokenToTreasuryVault<'info> {
96 pub authority: Signer<'info>,
98 pub store: UncheckedAccount<'info>,
101 #[account(
103 has_one = store,
104 )]
106 pub config: AccountLoader<'info, Config>,
107 #[account(mut, has_one = config)]
109 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
110 pub token: InterfaceAccount<'info, Mint>,
112 pub store_program: Program<'info, GmsolStore>,
114}
115
116pub(crate) fn unchecked_insert_token_to_treasury_vault(
120 ctx: Context<InsertTokenToTreasuryVault>,
121) -> Result<()> {
122 let token = ctx.accounts.token.key();
123 ctx.accounts
124 .treasury_vault_config
125 .load_mut()?
126 .insert_token(&token)?;
127 msg!(
128 "[Treasury] inserted a token into the treasury, token = {}",
129 token
130 );
131 Ok(())
132}
133
134impl<'info> WithStore<'info> for InsertTokenToTreasuryVault<'info> {
135 fn store_program(&self) -> AccountInfo<'info> {
136 self.store_program.to_account_info()
137 }
138
139 fn store(&self) -> AccountInfo<'info> {
140 self.store.to_account_info()
141 }
142}
143
144impl<'info> CpiAuthentication<'info> for InsertTokenToTreasuryVault<'info> {
145 fn authority(&self) -> AccountInfo<'info> {
146 self.authority.to_account_info()
147 }
148
149 fn on_error(&self) -> Result<()> {
150 err!(CoreError::PermissionDenied)
151 }
152}
153
154#[derive(Accounts)]
156pub struct RemoveTokenFromTreasuryVault<'info> {
157 pub authority: Signer<'info>,
159 pub store: UncheckedAccount<'info>,
162 #[account(
164 has_one = store,
165 )]
167 pub config: AccountLoader<'info, Config>,
168 #[account(mut, has_one = config)]
170 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
171 pub token: UncheckedAccount<'info>,
174 pub store_program: Program<'info, GmsolStore>,
176}
177
178pub(crate) fn unchecked_remove_token_from_treasury_vault(
182 ctx: Context<RemoveTokenFromTreasuryVault>,
183) -> Result<()> {
184 let token = ctx.accounts.token.key;
185 ctx.accounts
186 .treasury_vault_config
187 .load_mut()?
188 .remove_token(token)?;
189 msg!(
190 "[Treasury] removed a token from the treasury, token = {}",
191 token
192 );
193 Ok(())
194}
195
196impl<'info> WithStore<'info> for RemoveTokenFromTreasuryVault<'info> {
197 fn store_program(&self) -> AccountInfo<'info> {
198 self.store_program.to_account_info()
199 }
200
201 fn store(&self) -> AccountInfo<'info> {
202 self.store.to_account_info()
203 }
204}
205
206impl<'info> CpiAuthentication<'info> for RemoveTokenFromTreasuryVault<'info> {
207 fn authority(&self) -> AccountInfo<'info> {
208 self.authority.to_account_info()
209 }
210
211 fn on_error(&self) -> Result<()> {
212 err!(CoreError::PermissionDenied)
213 }
214}
215
216#[derive(Accounts)]
218pub struct ToggleTokenFlag<'info> {
219 pub authority: Signer<'info>,
221 pub store: UncheckedAccount<'info>,
224 #[account(
226 has_one = store,
227 )]
229 pub config: AccountLoader<'info, Config>,
230 #[account(mut, has_one = config)]
232 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
233 pub token: InterfaceAccount<'info, Mint>,
235 pub store_program: Program<'info, GmsolStore>,
237}
238
239pub(crate) fn unchecked_toggle_token_flag(
243 ctx: Context<ToggleTokenFlag>,
244 flag: &str,
245 value: bool,
246) -> Result<()> {
247 let previous = ctx
248 .accounts
249 .treasury_vault_config
250 .load_mut()?
251 .toggle_token_flag(
252 &ctx.accounts.token.key(),
253 flag.parse()
254 .map_err(|_| error!(CoreError::InvalidArgument))?,
255 value,
256 )?;
257 msg!(
258 "[Treasury] toggled token config flag {}: {} -> {}",
259 flag,
260 previous,
261 value
262 );
263 Ok(())
264}
265
266impl<'info> WithStore<'info> for ToggleTokenFlag<'info> {
267 fn store_program(&self) -> AccountInfo<'info> {
268 self.store_program.to_account_info()
269 }
270
271 fn store(&self) -> AccountInfo<'info> {
272 self.store.to_account_info()
273 }
274}
275
276impl<'info> CpiAuthentication<'info> for ToggleTokenFlag<'info> {
277 fn authority(&self) -> AccountInfo<'info> {
278 self.authority.to_account_info()
279 }
280
281 fn on_error(&self) -> Result<()> {
282 err!(CoreError::PermissionDenied)
283 }
284}
285
286#[derive(Accounts)]
288pub struct DepositToTreasuryVault<'info> {
289 #[account(mut)]
291 pub authority: Signer<'info>,
292 pub store: AccountLoader<'info, Store>,
294 #[account(
296 has_one = store,
297 constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
299 )]
300 pub config: AccountLoader<'info, Config>,
301 #[account(
303 has_one = config,
304 constraint = treasury_vault_config.load()?.is_deposit_allowed(&token.key())? @ CoreError::InvalidArgument,
305 )]
306 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
307 #[account(
309 seeds = [constants::RECEIVER_SEED, config.key().as_ref()],
310 bump,
311 )]
312 pub receiver: SystemAccount<'info>,
313 #[account(
315 has_one = store,
316 constraint = store.load()?.gt().exchange_time_window() as i64 == gt_exchange_vault.load()?.time_window() @ CoreError::InvalidArgument,
317 constraint = gt_exchange_vault.load()?.is_initialized() @ CoreError::InvalidArgument,
318 constraint = gt_exchange_vault.load()?.validate_depositable().map(|_| true)?,
319 seeds = [
320 GtExchangeVault::SEED,
321 store.key().as_ref(),
322 >_exchange_vault.load()?.time_window_index().to_le_bytes(),
323 >_exchange_vault.load()?.time_window_u32().to_le_bytes(),
324 ],
325 bump = gt_exchange_vault.load()?.bump,
326 seeds::program = gmsol_store::ID,
327 )]
328 pub gt_exchange_vault: AccountLoader<'info, GtExchangeVault>,
329 #[account(
331 mut,
332 has_one = treasury_vault_config,
333 has_one = gt_exchange_vault,
334 seeds = [
335 GtBank::SEED,
336 treasury_vault_config.key().as_ref(),
337 gt_exchange_vault.key().as_ref(),
338 ],
339 bump = gt_bank.load()?.bump,
340 )]
341 pub gt_bank: AccountLoader<'info, GtBank>,
342 pub token: InterfaceAccount<'info, Mint>,
344 #[account(
346 mut,
347 associated_token::authority = receiver,
348 associated_token::mint = token,
349 )]
350 pub receiver_vault: InterfaceAccount<'info, TokenAccount>,
351 #[account(
353 mut,
354 associated_token::authority = treasury_vault_config,
355 associated_token::mint = token,
356 )]
357 pub treasury_vault: InterfaceAccount<'info, TokenAccount>,
358 #[account(
360 mut,
361 associated_token::authority = gt_bank,
362 associated_token::mint = token,
363 )]
364 pub gt_bank_vault: InterfaceAccount<'info, TokenAccount>,
365 pub store_program: Program<'info, GmsolStore>,
367 pub token_program: Interface<'info, TokenInterface>,
369 pub associated_token_program: Program<'info, AssociatedToken>,
371}
372
373pub(crate) fn unchecked_deposit_to_treasury_vault(
377 ctx: Context<DepositToTreasuryVault>,
378) -> Result<()> {
379 use gmsol_model::utils::apply_factor;
380 use gmsol_store::constants::{MARKET_DECIMALS, MARKET_USD_UNIT};
381
382 let signer = ReceiverSigner::new(ctx.accounts.config.key(), ctx.bumps.receiver);
383 let decimals = ctx.accounts.token.decimals;
384
385 let (gt_amount, treasury_amount): (u64, u64) = {
386 let gt_factor = ctx.accounts.config.load()?.gt_factor();
387 let amount = u128::from(ctx.accounts.receiver_vault.amount);
388 require_gte!(MARKET_USD_UNIT, gt_factor, CoreError::Internal);
389 let gt_amount = apply_factor::<_, MARKET_DECIMALS>(&amount, >_factor)
390 .ok_or_else(|| error!(CoreError::Internal))?;
391 let treasury_amount = amount
392 .checked_sub(gt_amount)
393 .ok_or_else(|| error!(CoreError::Internal))?;
394 (
395 gt_amount
396 .try_into()
397 .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
398 treasury_amount
399 .try_into()
400 .map_err(|_| error!(CoreError::TokenAmountOverflow))?,
401 )
402 };
403
404 let cpi_ctx = ctx.accounts.transfer_checked_ctx_for_gt_bank();
405 transfer_checked(
406 cpi_ctx.with_signer(&[&signer.as_seeds()]),
407 gt_amount,
408 decimals,
409 )?;
410
411 ctx.accounts
412 .gt_bank
413 .load_mut()?
414 .record_transferred_in(&ctx.accounts.token.key(), gt_amount)?;
415
416 let cpi_ctx = ctx.accounts.transfer_checked_ctx_for_treasury();
417 transfer_checked(
418 cpi_ctx.with_signer(&[&signer.as_seeds()]),
419 treasury_amount,
420 decimals,
421 )?;
422 Ok(())
423}
424
425impl<'info> WithStore<'info> for DepositToTreasuryVault<'info> {
426 fn store_program(&self) -> AccountInfo<'info> {
427 self.store_program.to_account_info()
428 }
429
430 fn store(&self) -> AccountInfo<'info> {
431 self.store.to_account_info()
432 }
433}
434
435impl<'info> CpiAuthentication<'info> for DepositToTreasuryVault<'info> {
436 fn authority(&self) -> AccountInfo<'info> {
437 self.authority.to_account_info()
438 }
439
440 fn on_error(&self) -> Result<()> {
441 err!(CoreError::PermissionDenied)
442 }
443}
444
445impl<'info> DepositToTreasuryVault<'info> {
446 fn transfer_checked_ctx_for_treasury(
447 &self,
448 ) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
449 CpiContext::new(
450 self.token_program.to_account_info(),
451 TransferChecked {
452 from: self.receiver_vault.to_account_info(),
453 mint: self.token.to_account_info(),
454 to: self.treasury_vault.to_account_info(),
455 authority: self.receiver.to_account_info(),
456 },
457 )
458 }
459
460 fn transfer_checked_ctx_for_gt_bank(
461 &self,
462 ) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
463 CpiContext::new(
464 self.token_program.to_account_info(),
465 TransferChecked {
466 from: self.receiver_vault.to_account_info(),
467 mint: self.token.to_account_info(),
468 to: self.gt_bank_vault.to_account_info(),
469 authority: self.receiver.to_account_info(),
470 },
471 )
472 }
473}
474
475#[derive(Accounts)]
477pub struct WithdrawFromTreasuryVault<'info> {
478 #[account(mut)]
480 pub authority: Signer<'info>,
481 pub store: UncheckedAccount<'info>,
484 #[account(
486 has_one = store,
487 )]
488 pub config: AccountLoader<'info, Config>,
489 #[account(
491 has_one = config,
492 constraint = treasury_vault_config.load()?.is_withdrawal_allowed(&token.key())? @ CoreError::InvalidArgument,
493 )]
494 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
495 pub token: InterfaceAccount<'info, Mint>,
497 #[account(
499 mut,
500 associated_token::authority = treasury_vault_config,
501 associated_token::mint = token,
502 )]
503 pub treasury_vault: InterfaceAccount<'info, TokenAccount>,
504 #[account(mut, token::mint = token)]
506 pub target: InterfaceAccount<'info, TokenAccount>,
507 pub store_program: Program<'info, GmsolStore>,
509 pub token_program: Interface<'info, TokenInterface>,
511}
512
513pub(crate) fn unchecked_withdraw_from_treasury_vault(
517 ctx: Context<WithdrawFromTreasuryVault>,
518 amount: u64,
519 decimals: u8,
520) -> Result<()> {
521 let signer = ctx.accounts.treasury_vault_config.load()?.signer();
522 let cpi_ctx = ctx.accounts.transfer_checked_ctx();
523 transfer_checked(cpi_ctx.with_signer(&[&signer.as_seeds()]), amount, decimals)?;
524 Ok(())
525}
526
527impl<'info> WithStore<'info> for WithdrawFromTreasuryVault<'info> {
528 fn store_program(&self) -> AccountInfo<'info> {
529 self.store_program.to_account_info()
530 }
531
532 fn store(&self) -> AccountInfo<'info> {
533 self.store.to_account_info()
534 }
535}
536
537impl<'info> CpiAuthentication<'info> for WithdrawFromTreasuryVault<'info> {
538 fn authority(&self) -> AccountInfo<'info> {
539 self.authority.to_account_info()
540 }
541
542 fn on_error(&self) -> Result<()> {
543 err!(CoreError::PermissionDenied)
544 }
545}
546
547impl<'info> WithdrawFromTreasuryVault<'info> {
548 fn transfer_checked_ctx(&self) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
549 CpiContext::new(
550 self.token_program.to_account_info(),
551 TransferChecked {
552 from: self.treasury_vault.to_account_info(),
553 mint: self.token.to_account_info(),
554 to: self.target.to_account_info(),
555 authority: self.treasury_vault_config.to_account_info(),
556 },
557 )
558 }
559}
560
561#[derive(Accounts)]
571pub struct ConfirmGtBuyback<'info> {
572 #[account(mut)]
574 pub authority: Signer<'info>,
575 #[account(mut)]
577 pub store: AccountLoader<'info, Store>,
578 #[account(
580 has_one = store,
581 constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
583 )]
584 pub config: AccountLoader<'info, Config>,
585 #[account(
587 has_one = config,
588 )]
589 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
590 #[account(
592 mut,
593 has_one = store,
594 constraint = gt_exchange_vault.load()?.is_initialized() @ CoreError::InvalidArgument,
595 constraint = gt_exchange_vault.load()?.validate_confirmable().map(|_| true)? @ CoreError::InvalidArgument,
596 )]
597 pub gt_exchange_vault: AccountLoader<'info, GtExchangeVault>,
598 #[account(
600 mut,
601 has_one = treasury_vault_config,
602 has_one = gt_exchange_vault,
603 )]
604 pub gt_bank: AccountLoader<'info, GtBank>,
605 pub token_map: UncheckedAccount<'info>,
608 #[account(mut)]
611 pub oracle: AccountLoader<'info, Oracle>,
612 pub event_authority: UncheckedAccount<'info>,
615 pub store_program: Program<'info, GmsolStore>,
617 pub chainlink_program: Option<Program<'info, Chainlink>>,
619}
620
621pub(crate) fn unchecked_confirm_gt_buyback<'info>(
625 ctx: Context<'_, '_, 'info, 'info, ConfirmGtBuyback<'info>>,
626) -> Result<()> {
627 ctx.accounts.execute(ctx.remaining_accounts)
628}
629
630impl<'info> WithStore<'info> for ConfirmGtBuyback<'info> {
631 fn store_program(&self) -> AccountInfo<'info> {
632 self.store_program.to_account_info()
633 }
634
635 fn store(&self) -> AccountInfo<'info> {
636 self.store.to_account_info()
637 }
638}
639
640impl<'info> CpiAuthentication<'info> for ConfirmGtBuyback<'info> {
641 fn authority(&self) -> AccountInfo<'info> {
642 self.authority.to_account_info()
643 }
644
645 fn on_error(&self) -> Result<()> {
646 err!(CoreError::PermissionDenied)
647 }
648}
649
650impl<'info> ConfirmGtBuyback<'info> {
651 fn validate_and_split_remaining_accounts(
652 &self,
653 remaining_accounts: &'info [AccountInfo<'info>],
654 num_tokens: usize,
655 ) -> Result<(&'info [AccountInfo<'info>], &'info [AccountInfo<'info>])> {
656 let num_treasury_tokens = self.treasury_vault_config.load()?.num_tokens();
657 let treasury_tokens_end = num_tokens
658 .checked_add(num_treasury_tokens)
659 .ok_or_else(|| error!(CoreError::Internal))?;
660 let end = treasury_tokens_end
661 .checked_add(num_treasury_tokens)
662 .ok_or_else(|| error!(CoreError::Internal))?;
663 require_gte!(
664 remaining_accounts.len(),
665 end,
666 ErrorCode::AccountNotEnoughKeys
667 );
668
669 let feeds = &remaining_accounts[0..num_tokens];
670 let mints = &remaining_accounts[num_tokens..treasury_tokens_end];
671 let vaults = &remaining_accounts[treasury_tokens_end..end];
672
673 let treasury_vault_config_key = self.treasury_vault_config.key();
674 for (idx, token) in self.treasury_vault_config.load()?.tokens().enumerate() {
675 let mint = &mints[idx];
676 require_keys_eq!(mint.key(), token, CoreError::TokenMintMismatched);
677 let vault = &vaults[idx];
678 require_keys_eq!(*mint.owner, *vault.owner, CoreError::InvalidArgument);
679
680 let token_program_id = mint.owner;
681
682 let mint = InterfaceAccount::<Mint>::try_from(mint)?;
683 let vault = InterfaceAccount::<TokenAccount>::try_from(vault)?;
684
685 require_keys_eq!(vault.mint, mint.key(), CoreError::TokenMintMismatched);
686 require_keys_eq!(
687 vault.owner,
688 treasury_vault_config_key,
689 CoreError::InvalidArgument
690 );
691 require!(
692 is_associated_token_account_with_program_id(
693 &vault.key(),
694 &treasury_vault_config_key,
695 &token,
696 token_program_id
697 ),
698 CoreError::InvalidArgument
699 );
700 }
701
702 Ok((feeds, vaults))
703 }
704
705 fn execute(&mut self, remaining_accounts: &'info [AccountInfo<'info>]) -> Result<()> {
706 let signer = self.config.load()?.signer();
707
708 let total_gt_amount = self.gt_exchange_vault.load()?.amount();
710 let ctx = self.confirm_gt_exchange_vault_ctx();
711 confirm_gt_exchange_vault(ctx.with_signer(&[&signer.as_seeds()]))?;
712 self.gt_bank.load_mut()?.unchecked_confirm(total_gt_amount);
713
714 let tokens = self
715 .gt_bank
716 .load()?
717 .tokens()
718 .chain(self.treasury_vault_config.load()?.tokens())
719 .collect::<BTreeSet<_>>();
720
721 let (feeds, vaults) =
722 self.validate_and_split_remaining_accounts(remaining_accounts, tokens.len())?;
723
724 let ctx = self.set_prices_from_price_feed_ctx();
726 set_prices_from_price_feed(
727 ctx.with_signer(&[&signer.as_seeds()])
728 .with_remaining_accounts(feeds.to_vec()),
729 tokens.iter().copied().collect(),
730 )?;
731
732 self.update_balances(vaults)?;
733
734 let ctx = self.clear_all_prices_ctx();
736 clear_all_prices(ctx.with_signer(&[&signer.as_seeds()]))?;
737
738 Ok(())
739 }
740
741 fn set_prices_from_price_feed_ctx(
742 &self,
743 ) -> CpiContext<'_, '_, '_, 'info, SetPricesFromPriceFeed<'info>> {
744 CpiContext::new(
745 self.store_program.to_account_info(),
746 SetPricesFromPriceFeed {
747 authority: self.config.to_account_info(),
748 store: self.store.to_account_info(),
749 oracle: self.oracle.to_account_info(),
750 token_map: self.token_map.to_account_info(),
751 chainlink_program: self.chainlink_program.as_ref().map(|a| a.to_account_info()),
752 },
753 )
754 }
755
756 fn clear_all_prices_ctx(&self) -> CpiContext<'_, '_, '_, 'info, ClearAllPrices<'info>> {
757 CpiContext::new(
758 self.store_program.to_account_info(),
759 ClearAllPrices {
760 authority: self.config.to_account_info(),
761 store: self.store.to_account_info(),
762 oracle: self.oracle.to_account_info(),
763 },
764 )
765 }
766
767 fn confirm_gt_exchange_vault_ctx(
768 &self,
769 ) -> CpiContext<'_, '_, '_, 'info, ConfirmGtExchangeVault<'info>> {
770 CpiContext::new(
771 self.store_program.to_account_info(),
772 ConfirmGtExchangeVault {
773 authority: self.config.to_account_info(),
774 store: self.store.to_account_info(),
775 vault: self.gt_exchange_vault.to_account_info(),
776 event_authority: self.event_authority.to_account_info(),
777 program: self.store_program.to_account_info(),
778 },
779 )
780 }
781
782 fn get_max_buyback_value(&self, vaults: &[AccountInfo<'info>]) -> Result<BuybackValue> {
783 use anchor_spl::token::accessor;
784 use gmsol_model::utils::apply_factor;
785
786 let oracle = self.oracle.load()?;
787 let gt_bank_value = self.gt_bank.load()?.total_value(&oracle)?;
788
789 let mut treasury_value = 0u128;
790 for vault in vaults {
791 let token = accessor::mint(vault)?;
792 let amount = accessor::amount(vault)?;
793 let price = oracle.get_primary_price(&token, false)?.min;
794 let value = u128::from(amount)
795 .checked_mul(price)
796 .ok_or_else(|| error!(CoreError::ValueOverflow))?;
797 if value != 0 {
798 treasury_value = treasury_value
799 .checked_add(value)
800 .ok_or_else(|| error!(CoreError::ValueOverflow))?;
801 }
802 }
803
804 let total_vaule = treasury_value
805 .checked_add(gt_bank_value)
806 .ok_or_else(|| error!(CoreError::ValueOverflow))?;
807 let buyback_factor = self.config.load()?.buyback_factor();
808 let max_buyback_value = apply_factor::<_, { gmsol_store::constants::MARKET_DECIMALS }>(
809 &total_vaule,
810 &buyback_factor,
811 )
812 .ok_or_else(|| error!(CoreError::ValueOverflow))?;
813
814 Ok(BuybackValue::new(gt_bank_value, max_buyback_value))
815 }
816
817 fn update_balances(&self, vaults: &[AccountInfo<'info>]) -> Result<()> {
823 let buyback_amount = self.gt_exchange_vault.load()?.amount();
824
825 if buyback_amount == 0 {
826 self.gt_bank.load_mut()?.record_all_transferred_out()?;
827 return Ok(());
828 }
829
830 let BuybackValue {
831 max_buyback_value,
832 gt_bank_value,
833 } = self.get_max_buyback_value(vaults)?;
834
835 let buyback_amount = u128::from(self.gt_exchange_vault.load()?.amount());
836
837 let estimated_buyback_price = max_buyback_value
838 .checked_div(buyback_amount)
839 .ok_or_else(|| error!(CoreError::Internal))?;
840
841 let max_buyback_price = self.store.load()?.gt().minting_cost();
842
843 let buyback_price = estimated_buyback_price.min(max_buyback_price);
844 let buyback_value = buyback_amount
845 .checked_mul(buyback_price)
846 .ok_or_else(|| error!(CoreError::ValueOverflow))?;
847
848 if buyback_value == 0 {
849 self.gt_bank.load_mut()?.record_all_transferred_out()?;
850 return Ok(());
851 }
852
853 msg!(
854 "[Treasury] will buyback {} (unit) GT with value: {}",
855 buyback_price,
856 buyback_value,
857 );
858
859 self.gt_bank
861 .load_mut()?
862 .reserve_balances(&buyback_value, >_bank_value)?;
863
864 Ok(())
865 }
866}
867
868struct BuybackValue {
869 gt_bank_value: u128,
870 max_buyback_value: u128,
871}
872
873impl BuybackValue {
874 fn new(gt_bank_value: u128, max_buyback_value: u128) -> Self {
875 Self {
876 gt_bank_value,
877 max_buyback_value: gt_bank_value.min(max_buyback_value),
878 }
879 }
880}