gmsol_treasury/instructions/
treasury.rs

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/// The accounts definition for [`initialize_treasury_vault_config`](crate::gmsol_treasury::initialize_treasury_vault_config).
31#[derive(Accounts)]
32#[instruction(index: u16)]
33pub struct InitializeTreasuryVaultConfig<'info> {
34    /// Authority.
35    #[account(mut)]
36    pub authority: Signer<'info>,
37    /// Store.
38    /// CHECK: check by CPI.
39    pub store: UncheckedAccount<'info>,
40    /// Config to initialize with.
41    #[account(has_one = store)]
42    pub config: AccountLoader<'info, Config>,
43    /// Treasury vault config account to initialize.
44    #[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    /// Store program.
53    pub store_program: Program<'info, GmsolStore>,
54    /// The system program.
55    pub system_program: Program<'info, System>,
56}
57
58/// Initialize [`Treasury`] account.
59/// # CHECK
60/// Only [`TREASURY_ADMIN`](crate::roles::TREASURY_ADMIN) can use.
61pub(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/// The accounts definition for [`insert_token_to_treasury_vault`](crate::gmsol_treasury::insert_token_to_treasury_vault).
94#[derive(Accounts)]
95pub struct InsertTokenToTreasuryVault<'info> {
96    /// Authority.
97    pub authority: Signer<'info>,
98    /// Store.
99    /// CHECK: check by CPI.
100    pub store: UncheckedAccount<'info>,
101    /// Config.
102    #[account(
103        has_one = store,
104        // Insert to an unauthorized treasury vault config is allowed.
105    )]
106    pub config: AccountLoader<'info, Config>,
107    /// Treasury vault config.
108    #[account(mut, has_one = config)]
109    pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
110    /// Token to insert.
111    pub token: InterfaceAccount<'info, Mint>,
112    /// Store program.
113    pub store_program: Program<'info, GmsolStore>,
114}
115
116/// Insert a token to the [`Treasury`] account.
117/// # CHECK
118/// Only [`TREASURY_ADMIN`](crate::roles::TREASURY_ADMIN) can use.
119pub(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/// The accounts definition for [`remove_token_from_treasury_vault`](crate::gmsol_treasury::remove_token_from_treasury_vault).
155#[derive(Accounts)]
156pub struct RemoveTokenFromTreasuryVault<'info> {
157    /// Authority.
158    pub authority: Signer<'info>,
159    /// Store.
160    /// CHECK: check by CPI.
161    pub store: UncheckedAccount<'info>,
162    /// Config.
163    #[account(
164        has_one = store,
165        // Remove from an unauthorized treasury vault config is allowed.
166    )]
167    pub config: AccountLoader<'info, Config>,
168    /// Treasury Vault Config.
169    #[account(mut, has_one = config)]
170    pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
171    /// Token to remove.
172    /// CHECK: only used as a identifier.
173    pub token: UncheckedAccount<'info>,
174    /// Store program.
175    pub store_program: Program<'info, GmsolStore>,
176}
177
178/// Remove a token from the [`Treasury`] account.
179/// # CHECK
180/// Only [`TREASURY_ADMIN`](crate::roles::TREASURY_ADMIN) can use.
181pub(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/// The accounts definition for [`toggle_token_flag`](crate::gmsol_treasury::toggle_token_flag).
217#[derive(Accounts)]
218pub struct ToggleTokenFlag<'info> {
219    /// Authority.
220    pub authority: Signer<'info>,
221    /// Store.
222    /// CHECK: check by CPI.
223    pub store: UncheckedAccount<'info>,
224    /// Config.
225    #[account(
226        has_one = store,
227        // Toggle flags of an unauthorized treasury vault config is allowed.
228    )]
229    pub config: AccountLoader<'info, Config>,
230    /// Treasury Vault Config.
231    #[account(mut, has_one = config)]
232    pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
233    /// Token.
234    pub token: InterfaceAccount<'info, Mint>,
235    /// Store program.
236    pub store_program: Program<'info, GmsolStore>,
237}
238
239/// Toggle a token flag.
240/// # CHECK
241/// Only [`TREASURY_ADMIN`](crate::roles::TREASURY_ADMIN) can use.
242pub(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/// The accounts definition for [`deposit_to_treasury_vault`](crate::gmsol_treasury::deposit_to_treasury_vault).
287#[derive(Accounts)]
288pub struct DepositToTreasuryVault<'info> {
289    /// Authority.
290    #[account(mut)]
291    pub authority: Signer<'info>,
292    /// Store.
293    pub store: AccountLoader<'info, Store>,
294    /// Config.
295    #[account(
296        has_one = store,
297        // Only allow depositing into the authorized treausry vault.
298        constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
299    )]
300    pub config: AccountLoader<'info, Config>,
301    /// Treasury Config.
302    #[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    /// Receiver.
308    #[account(
309        seeds = [constants::RECEIVER_SEED, config.key().as_ref()],
310        bump,
311    )]
312    pub receiver: SystemAccount<'info>,
313    /// GT exchange vault.
314    #[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            &gt_exchange_vault.load()?.time_window_index().to_le_bytes(),
323            &gt_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    /// GT bank.
330    #[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    /// Token.
343    pub token: InterfaceAccount<'info, Mint>,
344    /// Receiver vault.
345    #[account(
346        mut,
347        associated_token::authority = receiver,
348        associated_token::mint = token,
349    )]
350    pub receiver_vault: InterfaceAccount<'info, TokenAccount>,
351    /// Treasury vault.
352    #[account(
353        mut,
354        associated_token::authority = treasury_vault_config,
355        associated_token::mint = token,
356    )]
357    pub treasury_vault: InterfaceAccount<'info, TokenAccount>,
358    /// GT bank vault.
359    #[account(
360        mut,
361        associated_token::authority = gt_bank,
362        associated_token::mint = token,
363    )]
364    pub gt_bank_vault: InterfaceAccount<'info, TokenAccount>,
365    /// Store program.
366    pub store_program: Program<'info, GmsolStore>,
367    /// The token program.
368    pub token_program: Interface<'info, TokenInterface>,
369    /// Associated token program.
370    pub associated_token_program: Program<'info, AssociatedToken>,
371}
372
373/// Deposit tokens from the receiver vault to the treasury vault.
374/// # CHECK
375/// Only [`TREASURY_KEEPER`](crate::roles::TREASURY_KEEPER) can use.
376pub(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, &gt_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/// The accounts definition for [`withdraw_from_treasury_vault`](crate::gmsol_treasury::withdraw_from_treasury_vault).
476#[derive(Accounts)]
477pub struct WithdrawFromTreasuryVault<'info> {
478    /// Authority.
479    #[account(mut)]
480    pub authority: Signer<'info>,
481    /// Store.
482    /// CHECK: check by CPI.
483    pub store: UncheckedAccount<'info>,
484    /// Config.
485    #[account(
486        has_one = store,
487    )]
488    pub config: AccountLoader<'info, Config>,
489    /// Treasury Vault Config.
490    #[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    /// Token.
496    pub token: InterfaceAccount<'info, Mint>,
497    /// Treasury vault.
498    #[account(
499        mut,
500        associated_token::authority = treasury_vault_config,
501        associated_token::mint =  token,
502    )]
503    pub treasury_vault: InterfaceAccount<'info, TokenAccount>,
504    /// Target.
505    #[account(mut, token::mint = token)]
506    pub target: InterfaceAccount<'info, TokenAccount>,
507    /// Store program.
508    pub store_program: Program<'info, GmsolStore>,
509    /// The token program.
510    pub token_program: Interface<'info, TokenInterface>,
511}
512
513/// Withdraw tokens from the treasury vault.
514/// # CHECK
515/// Only [`TREASURY_WITHDRAWER`](crate::roles::TREASURY_WITHDRAWER) can use.
516pub(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/// The accounts definition for [`confirm_gt_buyback`](crate::gmsol_treasury::confirm_gt_buyback).
562///
563/// Remaining accounts expected by this instruction:
564///
565///   - 0..N. `[]` N feed accounts sorted by token addresses, where N represents the total number of tokens defined in
566///     the GT bank or the treasury vault config.
567///   - N..(N+M). `[]` M token mint accounts, where M represents the total number of tokens defined in
568///     the treasury vault config.
569///   - (N+M)..(N+2M). `[]` M treasury vault token accounts.
570#[derive(Accounts)]
571pub struct ConfirmGtBuyback<'info> {
572    /// Authority.
573    #[account(mut)]
574    pub authority: Signer<'info>,
575    /// Store.
576    #[account(mut)]
577    pub store: AccountLoader<'info, Store>,
578    /// Config.
579    #[account(
580        has_one = store,
581        // Only allow confirming buyback with the authorized treausry.
582        constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
583    )]
584    pub config: AccountLoader<'info, Config>,
585    /// Treasury Vault Config.
586    #[account(
587        has_one = config,
588    )]
589    pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
590    /// GT exchange vault.
591    #[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    /// GT Bank.
599    #[account(
600        mut,
601        has_one = treasury_vault_config,
602        has_one = gt_exchange_vault,
603    )]
604    pub gt_bank: AccountLoader<'info, GtBank>,
605    /// Token map.
606    /// CHECK: check by CPI.
607    pub token_map: UncheckedAccount<'info>,
608    /// Oracle.
609    /// CHECK: the permissions should be checked by the CPI.
610    #[account(mut)]
611    pub oracle: AccountLoader<'info, Oracle>,
612    /// Event authority.
613    /// CHECK: check by CPI.
614    pub event_authority: UncheckedAccount<'info>,
615    /// Store program.
616    pub store_program: Program<'info, GmsolStore>,
617    /// Chainlink program.
618    pub chainlink_program: Option<Program<'info, Chainlink>>,
619}
620
621/// Confirm GT buyback.
622/// # CHECK
623/// Only [`TREASURY_KEEPER`](crate::roles::TREASURY_KEEPER) can use.
624pub(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        // Confirm GT exchange vault first to make sure all preconditions are satified.
709        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        // Set prices.
725        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        // Clear prices.
735        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    /// Reserve the final GT bank balances eligible for buyback.
818    /// # Note
819    /// We do not actually execute token transfers; instead, we only update
820    /// the token balances recorded in the GT bank. This is because tokens exceeding
821    /// the recorded amount can be transferred to the treasury bank at any time.
822    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        // Reserve balances for buyback.
860        self.gt_bank
861            .load_mut()?
862            .reserve_balances(&buyback_value, &gt_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}