gmsol_treasury/states/
gt_bank.rs1use 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#[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 pub fn get_balance(&self, token: &Pubkey) -> Option<u64> {
70 self.balances.get(token).map(|b| b.amount)
71 }
72
73 #[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 #[cfg(feature = "utils")]
83 pub fn treasury_vault_config(&self) -> &Pubkey {
84 &self.treasury_vault_config
85 }
86
87 #[cfg(feature = "utils")]
89 pub fn gt_exchange_vault(&self) -> &Pubkey {
90 &self.gt_exchange_vault
91 }
92
93 pub fn num_tokens(&self) -> usize {
95 self.balances.len()
96 }
97
98 pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
100 self.balances
101 .entries()
102 .map(|(key, _)| Pubkey::new_from_array(*key))
103 }
104
105 #[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 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 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 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
248pub struct GtBankSigner {
250 treasury_vault_config: Pubkey,
251 gt_exchange_vault: Pubkey,
252 bump_bytes: [u8; 1],
253}
254
255impl GtBankSigner {
256 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#[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#[derive(num_enum::IntoPrimitive)]
287#[repr(u8)]
288pub enum GtBankFlags {
289 Initialized,
291 }
293
294gmsol_utils::flags!(GtBankFlags, MAX_FLAGS, u8);