1use anchor_lang::prelude::*;
2use anchor_spl::{
3 associated_token::AssociatedToken,
4 token::Token,
5 token_2022::{transfer_checked, Token2022, TransferChecked},
6 token_interface::{Mint, TokenAccount, TokenInterface},
7};
8use gmsol_store::{
9 cpi::{accounts::CloseGtExchange, close_gt_exchange},
10 program::GmsolStore,
11 states::{
12 gt::{GtExchange, GtExchangeVault},
13 Seed, Store,
14 },
15 utils::{token::validate_associated_token_account, CpiAuthentication, WithStore},
16 CoreError,
17};
18use gmsol_utils::InitSpace;
19
20use crate::states::{Config, GtBank, TreasuryVaultConfig};
21
22#[derive(Accounts)]
24pub struct PrepareGtBank<'info> {
25 #[account(mut)]
27 pub authority: Signer<'info>,
28 pub store: UncheckedAccount<'info>,
31 #[account(
33 has_one = store,
34 constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
36 )]
37 pub config: AccountLoader<'info, Config>,
38 #[account(
40 has_one = config,
41 )]
42 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
43 #[account(
45 has_one = store,
46 constraint = gt_exchange_vault.load()?.is_initialized() @ CoreError::InvalidArgument,
47 constraint = !gt_exchange_vault.load()?.is_confirmed() @ CoreError::InvalidArgument,
48 )]
49 pub gt_exchange_vault: AccountLoader<'info, GtExchangeVault>,
50 #[account(
52 init_if_needed,
53 payer = authority,
54 space = 8 + GtBank::INIT_SPACE,
55 seeds = [
56 GtBank::SEED,
57 treasury_vault_config.key().as_ref(),
58 gt_exchange_vault.key().as_ref(),
59 ],
60 bump,
61 )]
62 pub gt_bank: AccountLoader<'info, GtBank>,
63 pub store_program: Program<'info, GmsolStore>,
65 pub system_program: Program<'info, System>,
67}
68
69pub(crate) fn unchecked_prepare_gt_bank(ctx: Context<PrepareGtBank>) -> Result<()> {
73 let bump = ctx.bumps.gt_bank;
74 let treasury_vault_config = ctx.accounts.treasury_vault_config.key();
75 let gt_exchange_vault = ctx.accounts.gt_exchange_vault.key();
76
77 match ctx.accounts.gt_bank.load_init() {
78 Ok(mut gt_bank) => {
79 gt_bank.try_init(bump, treasury_vault_config, gt_exchange_vault)?;
80 drop(gt_bank);
81 ctx.accounts.gt_bank.exit(&crate::ID)?;
82 }
83 Err(Error::AnchorError(err)) => {
84 if err.error_code_number != ErrorCode::AccountDiscriminatorAlreadySet as u32 {
85 return Err(Error::AnchorError(err));
86 }
87 }
88 Err(err) => {
89 return Err(err);
90 }
91 }
92
93 {
95 let gt_bank = ctx.accounts.gt_bank.load()?;
96 require_eq!(gt_bank.bump, bump, CoreError::InvalidArgument);
97 require_keys_eq!(
98 gt_bank.treasury_vault_config,
99 treasury_vault_config,
100 CoreError::InvalidArgument
101 );
102 require_keys_eq!(
103 gt_bank.gt_exchange_vault,
104 gt_exchange_vault,
105 CoreError::InvalidArgument
106 );
107 require!(gt_bank.is_initialized(), CoreError::InvalidArgument);
108 }
109
110 Ok(())
111}
112
113impl<'info> WithStore<'info> for PrepareGtBank<'info> {
114 fn store_program(&self) -> AccountInfo<'info> {
115 self.store_program.to_account_info()
116 }
117
118 fn store(&self) -> AccountInfo<'info> {
119 self.store.to_account_info()
120 }
121}
122
123impl<'info> CpiAuthentication<'info> for PrepareGtBank<'info> {
124 fn authority(&self) -> AccountInfo<'info> {
125 self.authority.to_account_info()
126 }
127
128 fn on_error(&self) -> Result<()> {
129 err!(CoreError::PermissionDenied)
130 }
131}
132
133#[derive(Accounts)]
135pub struct SyncGtBank<'info> {
136 #[account(mut)]
138 pub authority: Signer<'info>,
139 pub store: UncheckedAccount<'info>,
142 #[account(
144 has_one = store,
145 constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
147 )]
148 pub config: AccountLoader<'info, Config>,
149 #[account(
151 has_one = config,
152 constraint = treasury_vault_config.load()?.is_withdrawal_allowed(&token.key())? @ CoreError::InvalidArgument,
157 )]
158 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
159 #[account(
161 mut,
162 has_one = treasury_vault_config,
163 )]
164 pub gt_bank: AccountLoader<'info, GtBank>,
165 pub token: InterfaceAccount<'info, Mint>,
167 #[account(
169 mut,
170 associated_token::authority = treasury_vault_config,
171 associated_token::mint = token,
172 )]
173 pub treasury_vault: InterfaceAccount<'info, TokenAccount>,
174 #[account(
176 mut,
177 associated_token::authority = gt_bank,
178 associated_token::mint = token,
179 )]
180 pub gt_bank_vault: InterfaceAccount<'info, TokenAccount>,
181 pub store_program: Program<'info, GmsolStore>,
183 pub token_program: Interface<'info, TokenInterface>,
185 pub associated_token_program: Program<'info, AssociatedToken>,
187}
188
189pub(crate) fn unchecked_sync_gt_bank(ctx: Context<SyncGtBank>) -> Result<()> {
193 let delta = {
194 let gt_bank = ctx.accounts.gt_bank.load_mut()?;
195 let token = ctx.accounts.token.key();
196
197 let recorded_balance = gt_bank.get_balance(&token).unwrap_or(0);
198 let balance = ctx.accounts.gt_bank_vault.amount;
199
200 balance
201 .checked_sub(recorded_balance)
202 .ok_or_else(|| error!(CoreError::NotEnoughTokenAmount))?
203 };
204
205 require_neq!(delta, 0, CoreError::PreconditionsAreNotMet);
207
208 let cpi_ctx = ctx.accounts.transfer_checked_ctx();
209 let signer = ctx.accounts.gt_bank.load()?.signer();
210 transfer_checked(
211 cpi_ctx.with_signer(&[&signer.as_seeds()]),
212 delta,
213 ctx.accounts.token.decimals,
214 )?;
215 msg!(
216 "[Treasury] Synced GT Bank balance, deposit exceeding {} tokens into treasury",
217 delta
218 );
219
220 Ok(())
221}
222
223impl<'info> WithStore<'info> for SyncGtBank<'info> {
224 fn store_program(&self) -> AccountInfo<'info> {
225 self.store_program.to_account_info()
226 }
227
228 fn store(&self) -> AccountInfo<'info> {
229 self.store.to_account_info()
230 }
231}
232
233impl<'info> CpiAuthentication<'info> for SyncGtBank<'info> {
234 fn authority(&self) -> AccountInfo<'info> {
235 self.authority.to_account_info()
236 }
237
238 fn on_error(&self) -> Result<()> {
239 err!(CoreError::PermissionDenied)
240 }
241}
242
243impl<'info> SyncGtBank<'info> {
244 fn transfer_checked_ctx(&self) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
245 CpiContext::new(
246 self.token_program.to_account_info(),
247 TransferChecked {
248 from: self.gt_bank_vault.to_account_info(),
249 mint: self.token.to_account_info(),
250 to: self.treasury_vault.to_account_info(),
251 authority: self.gt_bank.to_account_info(),
252 },
253 )
254 }
255}
256
257#[derive(Accounts)]
266pub struct CompleteGtExchange<'info> {
267 pub owner: Signer<'info>,
269 #[account(constraint = store.load()?.validate_not_restarted().map(|_| true)?)]
271 pub store: AccountLoader<'info, Store>,
272 #[account(
274 has_one = store,
275 constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
277 )]
278 pub config: AccountLoader<'info, Config>,
279 #[account(
281 has_one = config,
282 )]
283 pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
284 #[account(mut)]
287 pub gt_exchange_vault: UncheckedAccount<'info>,
288 #[account(
290 mut,
291 has_one = treasury_vault_config,
292 has_one = gt_exchange_vault,
293 )]
294 pub gt_bank: AccountLoader<'info, GtBank>,
295 #[account(mut)]
298 pub exchange: AccountLoader<'info, GtExchange>,
299 pub store_program: Program<'info, GmsolStore>,
301 pub token_program: Program<'info, Token>,
303 pub token_2022_program: Program<'info, Token2022>,
305}
306
307pub(crate) fn complete_gt_exchange<'info>(
308 ctx: Context<'_, '_, 'info, 'info, CompleteGtExchange<'info>>,
309) -> Result<()> {
310 let remaining_accounts = ctx.remaining_accounts;
311 ctx.accounts.execute(remaining_accounts)?;
312 Ok(())
313}
314
315impl<'info> CompleteGtExchange<'info> {
316 fn execute(&self, remaining_accounts: &'info [AccountInfo<'info>]) -> Result<()> {
317 use gmsol_model::num::MulDiv;
318
319 let signer = self.config.load()?.signer();
320
321 let gt_amount = self.exchange.load()?.amount();
322
323 let ctx = self.close_gt_exchange_ctx();
326 close_gt_exchange(ctx.with_signer(&[&signer.as_seeds()]))?;
327
328 if gt_amount == 0 {
329 return Ok(());
330 }
331
332 let len = self.gt_bank.load()?.num_tokens();
333 let gt_bank_tokens = self.gt_bank.load()?.tokens().collect::<Vec<_>>();
334 let total_len = len.checked_mul(3).expect("must not overflow");
335 require_gte!(remaining_accounts.len(), total_len);
336 let tokens = &remaining_accounts[0..len];
337 let vaults = &remaining_accounts[len..(2 * len)];
338 let targets = &remaining_accounts[(2 * len)..total_len];
339
340 {
342 let gt_bank_address = self.gt_bank.key();
343 let owner_address = self.owner.key();
344
345 let gt_bank_signer = self.gt_bank.load()?.signer();
346 let total_gt_amount = self.gt_bank.load()?.remaining_confirmed_gt_amount();
347
348 require_gte!(total_gt_amount, gt_amount, CoreError::Internal);
349
350 for (idx, token) in gt_bank_tokens.iter().enumerate() {
351 let balance = self.gt_bank.load()?.get_balance(token).expect("must exist");
352 if balance == 0 {
353 continue;
354 }
355 let amount = balance
356 .checked_mul_div(>_amount, &total_gt_amount)
357 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
358
359 let mint = &tokens[idx];
360 require_keys_eq!(*mint.key, *token, CoreError::InvalidArgument);
361 let token_program = if mint.owner == self.token_program.key {
362 self.token_program.to_account_info()
363 } else if mint.owner == self.token_2022_program.key {
364 self.token_2022_program.to_account_info()
365 } else {
366 return err!(CoreError::InvalidArgument);
367 };
368
369 let vault = &vaults[idx];
370 validate_associated_token_account(
371 vault,
372 >_bank_address,
373 token,
374 &token_program.key(),
375 )?;
376
377 let target = &targets[idx];
378 require_keys_eq!(
379 anchor_spl::token::accessor::authority(target)?,
380 owner_address
381 );
382
383 let mint = InterfaceAccount::<Mint>::try_from(mint)?;
384 let decimals = mint.decimals;
385
386 let ctx = CpiContext::new(
387 token_program,
388 TransferChecked {
389 from: vault.to_account_info(),
390 mint: mint.to_account_info(),
391 to: target.to_account_info(),
392 authority: self.gt_bank.to_account_info(),
393 },
394 );
395
396 transfer_checked(
397 ctx.with_signer(&[>_bank_signer.as_seeds()]),
398 amount,
399 decimals,
400 )?;
401
402 self.gt_bank
403 .load_mut()?
404 .record_transferred_out(token, amount)?;
405 }
406
407 self.gt_bank.load_mut()?.record_claimed(gt_amount)?;
408 }
409
410 Ok(())
411 }
412
413 fn close_gt_exchange_ctx(&self) -> CpiContext<'_, '_, '_, 'info, CloseGtExchange<'info>> {
414 CpiContext::new(
415 self.store_program.to_account_info(),
416 CloseGtExchange {
417 authority: self.config.to_account_info(),
418 store: self.store.to_account_info(),
419 owner: self.owner.to_account_info(),
420 vault: self.gt_exchange_vault.to_account_info(),
421 exchange: self.exchange.to_account_info(),
422 },
423 )
424 }
425}