1use anchor_lang::prelude::*;
31
32use crate::{constants, CoreError};
33
34use super::{user::UserHeader, Seed};
35
36pub use gmsol_utils::gt::get_time_window_index;
37
38const MAX_RANK: usize = 15;
39const MAX_FLAGS: usize = 8;
40
41#[zero_copy]
42#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
43pub struct GtState {
44 decimals: u8,
45 #[cfg_attr(feature = "debug", debug(skip))]
46 padding_0: [u8; 7],
47 pub(crate) last_minted_at: i64,
49 total_minted: u64,
50 grow_step_amount: u64,
52 grow_steps: u64,
53 supply: u64,
55 #[cfg_attr(feature = "debug", debug(skip))]
56 padding_1: [u8; 8],
57 gt_vault: u64,
59 #[cfg_attr(feature = "debug", debug(skip))]
60 padding_2: [u8; 16],
61 minting_cost_grow_factor: u128,
63 minting_cost: u128,
64 #[cfg_attr(feature = "debug", debug(skip))]
65 padding_3: [u8; 32],
66 exchange_time_window: u32,
67 #[cfg_attr(feature = "debug", debug(skip))]
68 padding_4: [u8; 12],
69 max_rank: u64,
70 ranks: [u64; MAX_RANK],
71 order_fee_discount_factors: [u128; MAX_RANK + 1],
72 referral_reward_factors: [u128; MAX_RANK + 1],
73 #[cfg_attr(feature = "debug", debug(skip))]
74 padding_5: [u8; 32],
75 #[cfg_attr(feature = "debug", debug(skip))]
76 reserved: [u8; 256],
77}
78
79impl GtState {
80 pub(crate) fn init(
81 &mut self,
82 decimals: u8,
83 initial_minting_cost: u128,
84 grow_factor: u128,
85 grow_step: u64,
86 ranks: &[u64],
87 ) -> Result<()> {
88 require!(!self.is_initialized(), CoreError::GTStateHasBeenInitialized);
89 require_eq!(self.last_minted_at, 0, CoreError::GTStateHasBeenInitialized);
90 require_eq!(self.total_minted, 0, CoreError::GTStateHasBeenInitialized);
91 require_eq!(self.supply, 0, CoreError::GTStateHasBeenInitialized);
92 require_eq!(self.gt_vault, 0, CoreError::GTStateHasBeenInitialized);
93
94 require!(grow_step != 0, CoreError::InvalidGTConfig);
95
96 let max_rank = ranks.len().min(MAX_RANK);
97 let ranks = &ranks[0..max_rank];
98
99 require!(
101 ranks.windows(2).all(|ab| {
102 if let [a, b] = &ab {
103 a < b
104 } else {
105 false
106 }
107 }),
108 CoreError::InvalidGTConfig
109 );
110
111 let clock = Clock::get()?;
112
113 self.decimals = decimals;
114 self.last_minted_at = clock.unix_timestamp;
115 self.grow_step_amount = grow_step;
116 self.minting_cost_grow_factor = grow_factor;
117 self.minting_cost = initial_minting_cost;
118
119 let target = &mut self.ranks[0..max_rank];
120 target.copy_from_slice(ranks);
121 self.max_rank = max_rank as u64;
122
123 self.exchange_time_window = constants::DEFAULT_GT_VAULT_TIME_WINDOW;
124
125 Ok(())
126 }
127
128 pub fn is_initialized(&self) -> bool {
130 self.grow_step_amount != 0
131 }
132
133 pub(crate) fn set_order_fee_discount_factors(&mut self, factors: &[u128]) -> Result<()> {
134 require_eq!(
135 factors.len(),
136 (self.max_rank + 1) as usize,
137 CoreError::InvalidArgument
138 );
139
140 require!(
141 factors
142 .iter()
143 .all(|factor| *factor <= constants::MARKET_USD_UNIT),
144 CoreError::InvalidArgument
145 );
146
147 let target = &mut self.order_fee_discount_factors[0..factors.len()];
148 target.copy_from_slice(factors);
149
150 Ok(())
151 }
152
153 pub(crate) fn set_referral_reward_factors(&mut self, factors: &[u128]) -> Result<()> {
154 require_eq!(
155 factors.len(),
156 (self.max_rank + 1) as usize,
157 CoreError::InvalidArgument
158 );
159
160 require!(
162 factors.windows(2).all(|ab| {
163 if let [a, b] = &ab {
164 a <= b
165 } else {
166 false
167 }
168 }),
169 CoreError::InvalidArgument
170 );
171
172 let target = &mut self.referral_reward_factors[0..factors.len()];
173 target.copy_from_slice(factors);
174
175 Ok(())
176 }
177
178 pub(crate) fn order_fee_discount_factor(&self, rank: u8) -> Result<u128> {
179 require_gte!(self.max_rank, rank as u64, CoreError::InvalidArgument);
180 Ok(self.order_fee_discount_factors[rank as usize])
181 }
182
183 pub(crate) fn referral_reward_factor(&self, rank: u8) -> Result<u128> {
184 require_gte!(self.max_rank, rank as u64, CoreError::InvalidArgument);
185 Ok(self.referral_reward_factors[rank as usize])
186 }
187
188 pub fn exchange_time_window(&self) -> u32 {
190 self.exchange_time_window
191 }
192
193 pub fn decimals(&self) -> u8 {
195 self.decimals
196 }
197
198 pub fn minting_cost(&self) -> u128 {
200 self.minting_cost
201 }
202
203 pub fn total_minted(&self) -> u64 {
205 self.total_minted
206 }
207
208 pub fn grow_steps(&self) -> u64 {
210 self.grow_steps
211 }
212
213 pub fn supply(&self) -> u64 {
215 self.supply
216 }
217
218 pub fn gt_vault(&self) -> u64 {
220 self.gt_vault
221 }
222
223 pub fn set_exchange_time_window(&mut self, window: u32) -> Result<()> {
225 require_neq!(window, 0, CoreError::InvalidArgument);
226 self.exchange_time_window = window;
227 Ok(())
228 }
229
230 fn next_minting_cost(&self, next_minted: u64) -> Result<Option<(u64, u128)>> {
231 use gmsol_model::utils::apply_factor;
232
233 require!(self.grow_step_amount != 0, CoreError::InvalidGTConfig);
234 let new_steps = next_minted / self.grow_step_amount;
235
236 if new_steps != self.grow_steps {
237 let mut minting_cost = self.minting_cost;
238 for _ in self.grow_steps..new_steps {
239 minting_cost = apply_factor::<_, { constants::MARKET_DECIMALS }>(
240 &minting_cost,
241 &self.minting_cost_grow_factor,
242 )
243 .ok_or_else(|| error!(CoreError::Internal))?;
244 }
245 Ok(Some((new_steps, minting_cost)))
246 } else {
247 Ok(None)
248 }
249 }
250
251 fn unchecked_update_rank(&self, user: &mut UserHeader) {
253 debug_assert!(self.ranks().len() < u8::MAX as usize);
254 let rank = match self.ranks().binary_search(&user.gt.amount) {
255 Ok(rank) => rank + 1,
256 Err(rank) => rank,
257 };
258
259 let rank = rank as u8;
260 if user.gt.rank != rank {
261 user.gt.rank = rank;
262 msg!("[GT] user rank updated, new rank = {}", rank);
263 }
264 }
265
266 #[inline(never)]
267 pub(crate) fn mint_to(&mut self, user: &mut UserHeader, amount: u64) -> Result<()> {
268 if amount != 0 {
269 let clock = Clock::get()?;
270
271 let next_gt_total_minted = self
273 .total_minted
274 .checked_add(amount)
275 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
276 let next_minting_cost = self.next_minting_cost(next_gt_total_minted)?;
277
278 let next_user_total_minted = user
280 .gt
281 .total_minted
282 .checked_add(amount)
283 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
284 let next_amount = user
285 .gt
286 .amount
287 .checked_add(amount)
288 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
289 let next_supply = self
290 .supply
291 .checked_add(amount)
292 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
293
294 if let Some((new_steps, new_minting_cost)) = next_minting_cost {
297 self.minting_cost = new_minting_cost;
298 self.grow_steps = new_steps;
299 }
300 self.total_minted = next_gt_total_minted;
301 self.last_minted_at = clock.unix_timestamp;
302
303 user.gt.total_minted = next_user_total_minted;
304 user.gt.amount = next_amount;
305 user.gt.last_minted_at = self.last_minted_at;
306 self.supply = next_supply;
307
308 self.unchecked_update_rank(user);
309 }
310 Ok(())
311 }
312
313 pub(crate) fn unchecked_burn_from(&mut self, user: &mut UserHeader, amount: u64) -> Result<()> {
321 if amount != 0 {
322 require_gte!(user.gt.amount, amount, CoreError::NotEnoughTokenAmount);
323 let next_amount = user
324 .gt
325 .amount
326 .checked_sub(amount)
327 .ok_or_else(|| error!(CoreError::Internal))?;
328
329 let next_supply = self
330 .supply
331 .checked_sub(amount)
332 .ok_or_else(|| error!(CoreError::Internal))?;
333
334 user.gt.amount = next_amount;
337 self.supply = next_supply;
338
339 self.unchecked_update_rank(user);
340 }
341 Ok(())
342 }
343
344 #[inline(never)]
345 pub(crate) fn get_mint_amount(&self, size_in_value: u128) -> Result<(u64, u128, u128)> {
346 let minting_cost = self.minting_cost;
347
348 require!(minting_cost != 0, CoreError::InvalidGTConfig);
349
350 let remainder = size_in_value % minting_cost;
351 let minted = (size_in_value / minting_cost)
352 .try_into()
353 .map_err(|_| error!(CoreError::TokenAmountOverflow))?;
354
355 let minted_value = size_in_value - remainder;
356
357 Ok((minted, minted_value, minting_cost))
358 }
359
360 pub(crate) fn ranks(&self) -> &[u64] {
361 &self.ranks[0..(self.max_rank as usize)]
362 }
363
364 pub(crate) fn unchecked_request_exchange(
377 &mut self,
378 user: &mut UserHeader,
379 vault: &mut GtExchangeVault,
380 exchange: &mut GtExchange,
381 amount: u64,
382 ) -> Result<()> {
383 require!(user.is_initialized(), CoreError::InvalidArgument);
384 require!(vault.is_initialized(), CoreError::InvalidArgument);
385 require!(exchange.is_initialized(), CoreError::InvalidArgument);
386
387 self.unchecked_burn_from(user, amount)?;
388
389 vault.add(amount)?;
390 exchange.add(amount)?;
391
392 Ok(())
393 }
394
395 pub(crate) fn unchecked_confirm_exchange_vault(
404 &mut self,
405 vault: &mut GtExchangeVault,
406 ) -> Result<()> {
407 require!(vault.is_initialized(), CoreError::InvalidArgument);
408
409 let amount = vault.confirm()?;
410
411 self.process_gt_vault(amount)?;
412
413 Ok(())
414 }
415
416 fn process_gt_vault(&mut self, amount: u64) -> Result<()> {
417 if amount != 0 {
418 let amount_for_vault = amount;
419
420 let next_gt_vault = self
421 .gt_vault
422 .checked_add(amount_for_vault)
423 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
424
425 self.gt_vault = next_gt_vault;
426 }
427 Ok(())
428 }
429}
430
431#[repr(u8)]
433#[non_exhaustive]
434#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
435pub enum GtExchangeVaultFlag {
436 Intiailized,
438 Comfirmed,
440 }
442
443gmsol_utils::flags!(GtExchangeVaultFlag, MAX_FLAGS, u8);
444
445#[account(zero_copy)]
447#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
448#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
449pub struct GtExchangeVault {
450 pub bump: u8,
452 flags: GtExchangeVaultFlagContainer,
453 padding: [u8; 6],
454 ts: i64,
455 time_window: i64,
456 amount: u64,
457 pub store: Pubkey,
459 #[cfg_attr(feature = "debug", debug(skip))]
460 #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
461 reserved: [u8; 64],
462}
463
464impl GtExchangeVault {
465 pub fn amount(&self) -> u64 {
467 self.amount
468 }
469
470 pub fn is_initialized(&self) -> bool {
472 self.flags.get_flag(GtExchangeVaultFlag::Intiailized)
473 }
474
475 pub fn is_confirmed(&self) -> bool {
477 self.flags.get_flag(GtExchangeVaultFlag::Comfirmed)
478 }
479
480 pub(crate) fn init(&mut self, bump: u8, store: &Pubkey, time_window: u32) -> Result<()> {
481 require!(!self.is_initialized(), CoreError::PreconditionsAreNotMet);
482
483 require!(time_window != 0, CoreError::InvalidArgument);
484
485 let clock = Clock::get()?;
486
487 self.bump = bump;
488 self.ts = clock.unix_timestamp;
489 self.store = *store;
490 self.flags.set_flag(GtExchangeVaultFlag::Intiailized, true);
491 self.time_window = i64::from(time_window);
492
493 Ok(())
494 }
495
496 pub fn time_window_index(&self) -> i64 {
498 get_time_window_index(self.ts, self.time_window)
499 }
500
501 pub fn time_window(&self) -> i64 {
503 self.time_window
504 }
505
506 pub fn time_window_u32(&self) -> u32 {
508 self.time_window.try_into().expect("invalid vault")
509 }
510
511 pub fn validate_confirmable(&self) -> Result<()> {
513 require!(self.is_initialized(), CoreError::PreconditionsAreNotMet);
514 require!(!self.is_confirmed(), CoreError::PreconditionsAreNotMet);
515
516 let clock = Clock::get()?;
517 let current_index = get_time_window_index(clock.unix_timestamp, self.time_window);
518
519 require_gt!(
520 current_index,
521 self.time_window_index(),
522 CoreError::PreconditionsAreNotMet
523 );
524
525 Ok(())
526 }
527
528 fn confirm(&mut self) -> Result<u64> {
530 self.validate_confirmable()?;
531 self.flags.set_flag(GtExchangeVaultFlag::Comfirmed, true);
532 Ok(self.amount)
533 }
534
535 pub fn validate_depositable(&self) -> Result<()> {
537 require!(!self.is_confirmed(), CoreError::PreconditionsAreNotMet);
538
539 let clock = Clock::get()?;
540 let current_index = get_time_window_index(clock.unix_timestamp, self.time_window);
541 require_eq!(
542 current_index,
543 self.time_window_index(),
544 CoreError::InvalidArgument
545 );
546 Ok(())
547 }
548
549 fn add(&mut self, amount: u64) -> Result<()> {
555 self.validate_depositable()?;
556
557 self.amount = self
558 .amount
559 .checked_add(amount)
560 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
561 Ok(())
562 }
563}
564
565impl Seed for GtExchangeVault {
566 const SEED: &'static [u8] = b"gt_exchange_vault";
567}
568
569impl gmsol_utils::InitSpace for GtExchangeVault {
570 const INIT_SPACE: usize = std::mem::size_of::<Self>();
571}
572
573#[repr(u8)]
575#[non_exhaustive]
576#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
577pub enum GtExchangeFlag {
578 Intiailized,
580 }
582
583gmsol_utils::flags!(GtExchangeFlag, MAX_FLAGS, u8);
584
585#[account(zero_copy)]
587#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
588#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
589pub struct GtExchange {
590 pub bump: u8,
592 flags: GtExchangeFlagContainer,
593 #[cfg_attr(feature = "debug", debug(skip))]
594 padding: [u8; 6],
595 amount: u64,
596 pub owner: Pubkey,
598 pub store: Pubkey,
600 pub vault: Pubkey,
602 #[cfg_attr(feature = "debug", debug(skip))]
603 #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
604 reserved: [u8; 64],
605}
606
607impl Default for GtExchange {
608 fn default() -> Self {
609 use bytemuck::Zeroable;
610
611 Self::zeroed()
612 }
613}
614
615impl GtExchange {
616 pub fn is_initialized(&self) -> bool {
618 self.flags.get_flag(GtExchangeFlag::Intiailized)
619 }
620
621 pub(crate) fn init(
622 &mut self,
623 bump: u8,
624 owner: &Pubkey,
625 store: &Pubkey,
626 vault: &Pubkey,
627 ) -> Result<()> {
628 require!(!self.is_initialized(), CoreError::PreconditionsAreNotMet);
629
630 self.bump = bump;
631 self.owner = *owner;
632 self.store = *store;
633 self.vault = *vault;
634
635 self.flags.set_flag(GtExchangeFlag::Intiailized, true);
636
637 Ok(())
638 }
639
640 fn add(&mut self, amount: u64) -> Result<()> {
642 self.amount = self
643 .amount
644 .checked_add(amount)
645 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
646 Ok(())
647 }
648
649 pub fn owner(&self) -> &Pubkey {
651 &self.owner
652 }
653
654 pub(crate) fn store(&self) -> &Pubkey {
655 &self.store
656 }
657
658 pub fn vault(&self) -> &Pubkey {
660 &self.vault
661 }
662
663 pub fn amount(&self) -> u64 {
665 self.amount
666 }
667}
668
669impl gmsol_utils::InitSpace for GtExchange {
670 const INIT_SPACE: usize = std::mem::size_of::<Self>();
671}
672
673impl Seed for GtExchange {
674 const SEED: &'static [u8] = b"gt_exchange";
675}