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#[derive(Accounts)]
17pub struct InitializeGt<'info> {
18 #[account(mut)]
20 pub authority: Signer<'info>,
21 #[account(mut)]
23 pub store: AccountLoader<'info, Store>,
24 pub system_program: Program<'info, System>,
25}
26
27pub(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#[derive(Accounts)]
79pub struct ConfigurateGt<'info> {
80 pub authority: Signer<'info>,
82 #[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
100pub(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
112pub(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#[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#[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 {
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#[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 #[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#[event_cpi]
315#[derive(Accounts)]
316pub struct ConfirmGtExchangeVault<'info> {
317 pub authority: Signer<'info>,
319 #[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
340pub(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 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#[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 #[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
397pub(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}