gmsol_store/instructions/
gt.rs

1use anchor_lang::prelude::*;
2use gmsol_utils::InitSpace;
3
4use crate::{
5    events::{EventEmitter, GtUpdated},
6    states::{
7        gt::{GtExchange, GtExchangeVault},
8        user::UserHeader,
9        Seed, Store,
10    },
11    utils::internal,
12    CoreError,
13};
14
15/// The accounts defintions for the [`initialize_gt`](crate::gmsol_store::initialize_gt) instruction.
16#[derive(Accounts)]
17pub struct InitializeGt<'info> {
18    /// Authority
19    #[account(mut)]
20    pub authority: Signer<'info>,
21    /// Store.
22    #[account(mut)]
23    pub store: AccountLoader<'info, Store>,
24    pub system_program: Program<'info, System>,
25}
26
27/// CHECK: only MARKET_KEEPER is allowed to use this instruction.
28pub(crate) fn unchecked_initialize_gt(
29    ctx: Context<InitializeGt>,
30    decimals: u8,
31    initial_minting_cost: u128,
32    grow_factor: u128,
33    grow_step: u64,
34    ranks: &[u64],
35) -> Result<()> {
36    ctx.accounts.initialize_gt_state(
37        decimals,
38        initial_minting_cost,
39        grow_factor,
40        grow_step,
41        ranks,
42    )?;
43    Ok(())
44}
45
46impl<'info> internal::Authentication<'info> for InitializeGt<'info> {
47    fn authority(&self) -> &Signer<'info> {
48        &self.authority
49    }
50
51    fn store(&self) -> &AccountLoader<'info, Store> {
52        &self.store
53    }
54}
55
56impl InitializeGt<'_> {
57    fn initialize_gt_state(
58        &self,
59        decimals: u8,
60        initial_minting_cost: u128,
61        grow_factor: u128,
62        grow_step: u64,
63        ranks: &[u64],
64    ) -> Result<()> {
65        let mut store = self.store.load_mut()?;
66        store.gt_mut().init(
67            decimals,
68            initial_minting_cost,
69            grow_factor,
70            grow_step,
71            ranks,
72        )?;
73        Ok(())
74    }
75}
76
77/// The accounts defintions for GT configuration instructions.
78#[derive(Accounts)]
79pub struct ConfigurateGt<'info> {
80    /// Authority.
81    pub authority: Signer<'info>,
82    /// Store.
83    #[account(
84        mut,
85        constraint = store.load()?.gt().is_initialized() @ CoreError::PreconditionsAreNotMet,
86    )]
87    pub store: AccountLoader<'info, Store>,
88}
89
90impl<'info> internal::Authentication<'info> for ConfigurateGt<'info> {
91    fn authority(&self) -> &Signer<'info> {
92        &self.authority
93    }
94
95    fn store(&self) -> &AccountLoader<'info, Store> {
96        &self.store
97    }
98}
99
100/// CHECK: only MARKET_KEEPER is authorized to use this instruction.
101pub(crate) fn unchecked_gt_set_order_fee_discount_factors(
102    ctx: Context<ConfigurateGt>,
103    factors: &[u128],
104) -> Result<()> {
105    ctx.accounts
106        .store
107        .load_mut()?
108        .gt_mut()
109        .set_order_fee_discount_factors(factors)
110}
111
112/// CHECK: only GT_CONTROLLER is authorized to use this instruction.
113pub(crate) fn unchecked_gt_set_referral_reward_factors(
114    ctx: Context<ConfigurateGt>,
115    factors: &[u128],
116) -> Result<()> {
117    ctx.accounts
118        .store
119        .load_mut()?
120        .gt_mut()
121        .set_referral_reward_factors(factors)
122}
123
124/// CHECK: only GT_CONTROLLER is authorized to use this instruction.
125#[cfg(feature = "test-only")]
126pub(crate) fn unchecked_gt_set_exchange_time_window(
127    ctx: Context<ConfigurateGt>,
128    window: u32,
129) -> Result<()> {
130    ctx.accounts
131        .store
132        .load_mut()?
133        .gt_mut()
134        .set_exchange_time_window(window)
135}
136
137/// The accounts definition for [`prepare_gt_exchange_vault`](crate::gmsol_store::prepare_gt_exchange_vault) instruction.
138#[derive(Accounts)]
139#[instruction(time_window_index: i64)]
140pub struct PrepareGtExchangeVault<'info> {
141    #[account(mut)]
142    pub payer: Signer<'info>,
143    #[account(constraint = store.load()?.validate_not_restarted()?.gt().is_initialized() @ CoreError::PreconditionsAreNotMet)]
144    pub store: AccountLoader<'info, Store>,
145    #[account(
146        init_if_needed,
147        space = 8 + GtExchangeVault::INIT_SPACE,
148        payer = payer,
149        seeds = [
150            GtExchangeVault::SEED,
151            store.key().as_ref(),
152            &time_window_index.to_le_bytes(),
153            &store.load()?.gt().exchange_time_window().to_le_bytes(),
154        ],
155        bump,
156    )]
157    pub vault: AccountLoader<'info, GtExchangeVault>,
158    pub system_program: Program<'info, System>,
159}
160
161pub(crate) fn prepare_gt_exchange_vault(
162    ctx: Context<PrepareGtExchangeVault>,
163    time_window_index: i64,
164) -> Result<()> {
165    let store = ctx.accounts.store.load()?;
166    let time_window = store.gt().exchange_time_window();
167
168    match ctx.accounts.vault.load_init() {
169        Ok(mut vault) => {
170            vault.init(ctx.bumps.vault, &ctx.accounts.store.key(), time_window)?;
171            drop(vault);
172            ctx.accounts.vault.exit(&crate::ID)?;
173        }
174        Err(Error::AnchorError(err)) => {
175            if err.error_code_number != ErrorCode::AccountDiscriminatorAlreadySet as u32 {
176                return Err(Error::AnchorError(err));
177            }
178        }
179        Err(err) => {
180            return Err(err);
181        }
182    }
183
184    // Validate the vault.
185    {
186        let vault = ctx.accounts.vault.load()?;
187        require!(vault.is_initialized(), CoreError::PreconditionsAreNotMet);
188        require_keys_eq!(
189            vault.store,
190            ctx.accounts.store.key(),
191            CoreError::StoreMismatched
192        );
193        require_eq!(
194            vault.time_window_index(),
195            time_window_index,
196            CoreError::InvalidArgument
197        );
198        require_eq!(
199            vault.time_window(),
200            time_window as i64,
201            CoreError::InvalidArgument
202        );
203    }
204
205    Ok(())
206}
207
208/// The accounts definition for [`request_gt_exchange`](crate::gmsol_store::request_gt_exchange) instruction.
209#[event_cpi]
210#[derive(Accounts)]
211pub struct RequestGtExchange<'info> {
212    #[account(mut)]
213    pub owner: Signer<'info>,
214    #[account(
215        mut,
216        constraint = store.load()?.validate_not_restarted()?.gt().is_initialized() @ CoreError::PreconditionsAreNotMet,
217    )]
218    pub store: AccountLoader<'info, Store>,
219    /// User Account.
220    #[account(
221        mut,
222        constraint = user.load()?.is_initialized() @ CoreError::InvalidUserAccount,
223        has_one = owner,
224        has_one = store,
225        seeds = [UserHeader::SEED, store.key().as_ref(), owner.key().as_ref()],
226        bump = user.load()?.bump,
227    )]
228    pub user: AccountLoader<'info, UserHeader>,
229    #[account(
230        mut,
231        constraint = vault.load()?.is_initialized() @ CoreError::InvalidArgument,
232        has_one = store,
233        seeds = [
234            GtExchangeVault::SEED,
235            store.key().as_ref(),
236            &vault.load()?.time_window_index().to_le_bytes(),
237            &vault.load()?.time_window_u32().to_le_bytes(),
238        ],
239        bump = vault.load()?.bump,
240    )]
241    pub vault: AccountLoader<'info, GtExchangeVault>,
242    #[account(
243        init_if_needed,
244        payer = owner,
245        space = 8 + GtExchange::INIT_SPACE,
246        seeds = [GtExchange::SEED, vault.key().as_ref(), owner.key().as_ref()],
247        bump,
248    )]
249    pub exchange: AccountLoader<'info, GtExchange>,
250    pub system_program: Program<'info, System>,
251}
252
253pub(crate) fn request_gt_exchange(ctx: Context<RequestGtExchange>, amount: u64) -> Result<()> {
254    let accounts = ctx.accounts;
255
256    accounts.validate_and_init_exchange_if_needed(ctx.bumps.exchange)?;
257
258    let mut store = accounts.store.load_mut()?;
259    let mut vault = accounts.vault.load_mut()?;
260    let mut user = accounts.user.load_mut()?;
261    let mut exchange = accounts.exchange.load_mut()?;
262
263    store
264        .gt_mut()
265        .unchecked_request_exchange(&mut user, &mut vault, &mut exchange, amount)?;
266
267    let event_emitter = EventEmitter::new(&accounts.event_authority, ctx.bumps.event_authority);
268    event_emitter.emit_cpi(&GtUpdated::burned(amount, store.gt(), Some(&user)))?;
269
270    Ok(())
271}
272
273impl RequestGtExchange<'_> {
274    fn validate_and_init_exchange_if_needed(&mut self, bump: u8) -> Result<()> {
275        match self.exchange.load_init() {
276            Ok(mut exchange) => {
277                exchange.init(
278                    bump,
279                    &self.owner.key(),
280                    &self.store.key(),
281                    &self.vault.key(),
282                )?;
283                drop(exchange);
284                self.exchange.exit(&crate::ID)?;
285            }
286            Err(Error::AnchorError(err)) => {
287                if err.error_code_number != ErrorCode::AccountDiscriminatorAlreadySet as u32 {
288                    return Err(Error::AnchorError(err));
289                }
290            }
291            Err(err) => {
292                return Err(err);
293            }
294        }
295        require!(
296            self.exchange.load()?.is_initialized(),
297            CoreError::PreconditionsAreNotMet
298        );
299        require_keys_eq!(
300            *self.exchange.load()?.owner(),
301            self.owner.key(),
302            CoreError::OwnerMismatched,
303        );
304        require_keys_eq!(
305            *self.exchange.load()?.store(),
306            self.store.key(),
307            CoreError::StoreMismatched,
308        );
309        Ok(())
310    }
311}
312
313/// The accounts definition for [`confirm_gt_exchange_vault`](crate::confirm_gt_exchange_vault) instruction.
314#[event_cpi]
315#[derive(Accounts)]
316pub struct ConfirmGtExchangeVault<'info> {
317    /// Authority.
318    pub authority: Signer<'info>,
319    /// Store.
320    #[account(
321        mut,
322        constraint = store.load()?.gt().is_initialized() @ CoreError::PreconditionsAreNotMet,
323    )]
324    pub store: AccountLoader<'info, Store>,
325    #[account(
326        mut,
327        constraint = vault.load()?.is_initialized() @ CoreError::InvalidUserAccount,
328        has_one = store,
329        seeds = [
330            GtExchangeVault::SEED,
331            store.key().as_ref(),
332            &vault.load()?.time_window_index().to_le_bytes(),
333            &vault.load()?.time_window_u32().to_le_bytes(),
334        ],
335        bump = vault.load()?.bump,
336    )]
337    pub vault: AccountLoader<'info, GtExchangeVault>,
338}
339
340/// CHECK: only GT_CONTROLLER is authorized to use this instruction.
341pub(crate) fn unchecked_confirm_gt_exchange_vault(
342    ctx: Context<ConfirmGtExchangeVault>,
343) -> Result<()> {
344    let mut store = ctx.accounts.store.load_mut()?;
345    let mut vault = ctx.accounts.vault.load_mut()?;
346    store
347        .gt_mut()
348        .unchecked_confirm_exchange_vault(&mut vault)?;
349
350    let event_emitter = EventEmitter::new(&ctx.accounts.event_authority, ctx.bumps.event_authority);
351    // Since no GT is minted, the rewarded amount is zero.
352    event_emitter.emit_cpi(&GtUpdated::rewarded(0, store.gt(), None))?;
353    Ok(())
354}
355
356impl<'info> internal::Authentication<'info> for ConfirmGtExchangeVault<'info> {
357    fn authority(&self) -> &Signer<'info> {
358        &self.authority
359    }
360
361    fn store(&self) -> &AccountLoader<'info, Store> {
362        &self.store
363    }
364}
365
366/// The accounts definition for [`close_gt_exchange`](crate::close_gt_exchange) instruction.
367#[derive(Accounts)]
368pub struct CloseGtExchange<'info> {
369    pub authority: Signer<'info>,
370    #[account(
371        constraint = store.load()?.gt().is_initialized() @ CoreError::PreconditionsAreNotMet,
372    )]
373    pub store: AccountLoader<'info, Store>,
374    /// CHECK: only used to receive the funds.
375    #[account(mut)]
376    pub owner: UncheckedAccount<'info>,
377    #[account(
378        mut,
379        constraint = vault.load()?.is_initialized() @ CoreError::InvalidArgument,
380        constraint = vault.load()?.is_confirmed() @ CoreError::PreconditionsAreNotMet,
381        has_one = store,
382    )]
383    pub vault: AccountLoader<'info, GtExchangeVault>,
384    #[account(
385        mut,
386        close = owner,
387        constraint = exchange.load()?.is_initialized() @ CoreError::InvalidArgument,
388        has_one = store,
389        has_one = owner,
390        has_one = vault,
391        seeds = [GtExchange::SEED, vault.key().as_ref(), owner.key().as_ref()],
392        bump = exchange.load()?.bump,
393    )]
394    pub exchange: AccountLoader<'info, GtExchange>,
395}
396
397/// CHECK: only GT_CONTROLLER is allowed to use this instruction.
398pub(crate) fn unchecked_close_gt_exchange(ctx: Context<CloseGtExchange>) -> Result<()> {
399    let vault = ctx.accounts.vault.load()?;
400    let exchange = ctx.accounts.exchange.load()?;
401    msg!(
402        "[GT] Closing confirmed exchange: vault_index = {}, vault = {}, owner = {}, amount = {}",
403        vault.time_window_index(),
404        exchange.vault(),
405        exchange.owner(),
406        exchange.amount()
407    );
408    Ok(())
409}
410
411impl<'info> internal::Authentication<'info> for CloseGtExchange<'info> {
412    fn authority(&self) -> &Signer<'info> {
413        &self.authority
414    }
415
416    fn store(&self) -> &AccountLoader<'info, Store> {
417        &self.store
418    }
419}