gmsol_treasury/states/
gt_bank.rs

1use anchor_lang::prelude::*;
2use bytemuck::Zeroable;
3use gmsol_store::{
4    states::{Oracle, Seed},
5    utils::pubkey::to_bytes,
6    CoreError,
7};
8
9use super::treasury::MAX_TOKENS;
10
11/// GT Bank.
12#[account(zero_copy)]
13#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
14pub struct GtBank {
15    version: u8,
16    pub(crate) bump: u8,
17    flags: GtBankFlagsContainer,
18    #[cfg_attr(feature = "debug", debug(skip))]
19    padding: [u8; 13],
20    pub(crate) treasury_vault_config: Pubkey,
21    pub(crate) gt_exchange_vault: Pubkey,
22    remaining_confirmed_gt_amount: u64,
23    #[cfg_attr(feature = "debug", debug(skip))]
24    reserved: [u8; 256],
25    balances: TokenBalances,
26}
27
28impl Seed for GtBank {
29    const SEED: &'static [u8] = b"gt_bank";
30}
31
32impl gmsol_utils::InitSpace for GtBank {
33    const INIT_SPACE: usize = std::mem::size_of::<Self>();
34}
35
36impl GtBank {
37    pub(crate) fn try_init(
38        &mut self,
39        bump: u8,
40        treasury_vault_config: Pubkey,
41        gt_exchange_vault: Pubkey,
42    ) -> Result<()> {
43        require!(
44            !self.flags.get_flag(GtBankFlags::Initialized),
45            CoreError::PreconditionsAreNotMet
46        );
47        self.bump = bump;
48        self.treasury_vault_config = treasury_vault_config;
49        self.gt_exchange_vault = gt_exchange_vault;
50        self.flags.set_flag(GtBankFlags::Initialized, true);
51        Ok(())
52    }
53
54    fn get_balance_or_insert(&mut self, token: &Pubkey) -> Result<&mut TokenBalance> {
55        if self.balances.get(token).is_none() {
56            self.balances
57                .insert_with_options(token, TokenBalance::default(), true)?;
58        }
59        self.get_balance_mut(token)
60    }
61
62    fn get_balance_mut(&mut self, token: &Pubkey) -> Result<&mut TokenBalance> {
63        self.balances
64            .get_mut(token)
65            .ok_or_else(|| error!(CoreError::NotFound))
66    }
67
68    /// Get balance of the given token
69    pub fn get_balance(&self, token: &Pubkey) -> Option<u64> {
70        self.balances.get(token).map(|b| b.amount)
71    }
72
73    /// Iterate over token balances.
74    #[cfg(feature = "utils")]
75    pub fn balances(&self) -> impl Iterator<Item = (Pubkey, u64)> + '_ {
76        self.balances
77            .entries()
78            .map(|(k, b)| (Pubkey::new_from_array(*k), b.amount))
79    }
80
81    /// Get treasury vault config address.
82    #[cfg(feature = "utils")]
83    pub fn treasury_vault_config(&self) -> &Pubkey {
84        &self.treasury_vault_config
85    }
86
87    /// Get GT exchange vault address.
88    #[cfg(feature = "utils")]
89    pub fn gt_exchange_vault(&self) -> &Pubkey {
90        &self.gt_exchange_vault
91    }
92
93    /// Get the number of tokens.
94    pub fn num_tokens(&self) -> usize {
95        self.balances.len()
96    }
97
98    /// Get all tokens.
99    pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
100        self.balances
101            .entries()
102            .map(|(key, _)| Pubkey::new_from_array(*key))
103    }
104
105    /// Create tokens with feed.
106    #[cfg(feature = "utils")]
107    pub fn to_feeds(
108        &self,
109        map: &impl gmsol_store::states::TokenMapAccess,
110        treasury_vault_config: &super::TreasuryVaultConfig,
111    ) -> Result<gmsol_store::states::common::TokensWithFeed> {
112        use std::collections::BTreeSet;
113
114        use gmsol_store::states::common::{TokenRecord, TokensWithFeed};
115
116        let tokens = self
117            .tokens()
118            .chain(treasury_vault_config.tokens())
119            .collect::<BTreeSet<_>>();
120        let records = tokens
121            .iter()
122            .map(|token| {
123                let config = map
124                    .get(token)
125                    .ok_or_else(|| error!(CoreError::UnknownToken))?;
126                TokenRecord::from_config(*token, config)
127                    .map_err(CoreError::from)
128                    .map_err(|err| error!(err))
129            })
130            .collect::<Result<Vec<_>>>()?;
131
132        TokensWithFeed::try_from_records(records)
133            .map_err(CoreError::from)
134            .map_err(|err| error!(err))
135    }
136
137    pub(crate) fn record_transferred_in(&mut self, token: &Pubkey, amount: u64) -> Result<()> {
138        let balance = self.get_balance_or_insert(token)?;
139        let next_balance = balance
140            .amount
141            .checked_add(amount)
142            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
143        balance.amount = next_balance;
144        Ok(())
145    }
146
147    pub(crate) fn record_transferred_out(&mut self, token: &Pubkey, amount: u64) -> Result<()> {
148        if amount == 0 {
149            return Ok(());
150        }
151
152        let balance = self.get_balance_mut(token)?;
153        let next_balance = balance
154            .amount
155            .checked_sub(amount)
156            .ok_or_else(|| error!(CoreError::NotEnoughTokenAmount))?;
157        balance.amount = next_balance;
158
159        Ok(())
160    }
161
162    pub(crate) fn record_all_transferred_out(&mut self) -> Result<()> {
163        self.balances.clear();
164        Ok(())
165    }
166
167    /// Returns whether the GT bank is initialized.
168    pub fn is_initialized(&self) -> bool {
169        self.flags.get_flag(GtBankFlags::Initialized)
170    }
171
172    pub(crate) fn total_value(&self, oracle: &Oracle) -> Result<u128> {
173        let mut total_value: u128 = 0;
174
175        for (token, balance) in self.balances.entries() {
176            let amount = u128::from(balance.amount);
177            if amount == 0 {
178                continue;
179            }
180            let token = Pubkey::new_from_array(*token);
181            let price = oracle.get_primary_price(&token, false)?.min;
182            let value = amount
183                .checked_mul(price)
184                .ok_or_else(|| error!(CoreError::ValueOverflow))?;
185
186            if value != 0 {
187                total_value = total_value
188                    .checked_add(value)
189                    .ok_or_else(|| error!(CoreError::ValueOverflow))?;
190            }
191        }
192
193        Ok(total_value)
194    }
195
196    /// Reserve the balances of the given proportion.
197    /// # Warning
198    /// This method is not atomic.
199    pub(crate) fn reserve_balances(&mut self, numerator: &u128, denominator: &u128) -> Result<()> {
200        use gmsol_model::num::MulDiv;
201
202        require_gte!(denominator, numerator, CoreError::InvalidArgument);
203
204        for (_key, balance) in self.balances.entries_mut() {
205            if balance.amount == 0 {
206                continue;
207            }
208            let reserve_balance = u128::from(balance.amount)
209                .checked_mul_div(numerator, denominator)
210                .and_then(|b| b.try_into().ok())
211                .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
212            require_gte!(balance.amount, reserve_balance, CoreError::Internal);
213            balance.amount = reserve_balance;
214        }
215
216        Ok(())
217    }
218
219    pub(crate) fn signer(&self) -> GtBankSigner {
220        GtBankSigner {
221            treasury_vault_config: self.treasury_vault_config,
222            gt_exchange_vault: self.gt_exchange_vault,
223            bump_bytes: [self.bump],
224        }
225    }
226
227    /// # CHECK
228    /// `gt_amount` must be the total amount of the GT exchange vault
229    /// of this bank and it must have been confirmed.
230    pub(crate) fn unchecked_confirm(&mut self, gt_amount: u64) {
231        self.remaining_confirmed_gt_amount = gt_amount;
232    }
233
234    pub(crate) fn record_claimed(&mut self, gt_amount: u64) -> Result<()> {
235        let next_amount = self
236            .remaining_confirmed_gt_amount
237            .checked_sub(gt_amount)
238            .ok_or_else(|| error!(CoreError::InvalidArgument))?;
239        self.remaining_confirmed_gt_amount = next_amount;
240        Ok(())
241    }
242
243    pub(crate) fn remaining_confirmed_gt_amount(&self) -> u64 {
244        self.remaining_confirmed_gt_amount
245    }
246}
247
248/// Gt Bank Signer.
249pub struct GtBankSigner {
250    treasury_vault_config: Pubkey,
251    gt_exchange_vault: Pubkey,
252    bump_bytes: [u8; 1],
253}
254
255impl GtBankSigner {
256    /// As signer seeds.
257    pub fn as_seeds(&self) -> [&[u8]; 4] {
258        [
259            GtBank::SEED,
260            self.treasury_vault_config.as_ref(),
261            self.gt_exchange_vault.as_ref(),
262            &self.bump_bytes,
263        ]
264    }
265}
266
267/// Token Balance.
268#[zero_copy]
269#[cfg_attr(feature = "debug", derive(Debug))]
270pub struct TokenBalance {
271    amount: u64,
272    reserved: [u8; 64],
273}
274
275impl Default for TokenBalance {
276    fn default() -> Self {
277        Self::zeroed()
278    }
279}
280
281gmsol_utils::fixed_map!(TokenBalances, Pubkey, to_bytes, TokenBalance, MAX_TOKENS, 4);
282
283const MAX_FLAGS: usize = 8;
284
285/// Flags of GT Bank.
286#[derive(num_enum::IntoPrimitive)]
287#[repr(u8)]
288pub enum GtBankFlags {
289    /// Initialized.
290    Initialized,
291    // CHECK: cannot have more than `MAX_FLAGS` flags.
292}
293
294gmsol_utils::flags!(GtBankFlags, MAX_FLAGS, u8);