gmsol/exchange/
auto_deleveraging.rs

1use std::{collections::HashMap, ops::Deref};
2
3use anchor_client::solana_sdk::{pubkey::Pubkey, signer::Signer};
4use gmsol_solana_utils::{
5    bundle_builder::{BundleBuilder, BundleOptions},
6    transaction_builder::TransactionBuilder,
7};
8use gmsol_store::states::{common::TokensWithFeed, Market, PriceProviderKind};
9use solana_sdk::address_lookup_table::AddressLookupTableAccount;
10
11use crate::{
12    store::utils::FeedsParser,
13    utils::{
14        builder::{
15            FeedAddressMap, FeedIds, MakeBundleBuilder, PullOraclePriceConsumer, SetExecutionFee,
16        },
17        fix_optional_account_metas,
18    },
19};
20
21/// The compute budget for `auto_deleverage`.
22pub const ADL_COMPUTE_BUDGET: u32 = 800_000;
23
24/// Update ADL state Instruction Builder.
25pub struct UpdateAdlBuilder<'a, C> {
26    client: &'a crate::Client<C>,
27    store: Pubkey,
28    market_token: Pubkey,
29    oracle: Pubkey,
30    for_long: bool,
31    for_short: bool,
32    hint: Option<UpdateAdlHint>,
33    feeds_parser: FeedsParser,
34    alts: HashMap<Pubkey, Vec<Pubkey>>,
35}
36
37impl<'a, C: Deref<Target = impl Signer> + Clone> UpdateAdlBuilder<'a, C> {
38    pub(super) fn try_new(
39        client: &'a crate::Client<C>,
40        store: &Pubkey,
41        oracle: &Pubkey,
42        market_token: &Pubkey,
43        for_long: bool,
44        for_short: bool,
45    ) -> crate::Result<Self> {
46        Ok(Self {
47            client,
48            store: *store,
49            market_token: *market_token,
50            oracle: *oracle,
51            for_long,
52            for_short,
53            hint: None,
54            feeds_parser: FeedsParser::default(),
55            alts: Default::default(),
56        })
57    }
58
59    /// Insert an Address Lookup Table.
60    pub fn add_alt(&mut self, account: AddressLookupTableAccount) -> &mut Self {
61        self.alts.insert(account.key, account.addresses);
62        self
63    }
64
65    /// Prepare hint for auto-deleveraging.
66    pub async fn prepare_hint(&mut self) -> crate::Result<UpdateAdlHint> {
67        match &self.hint {
68            Some(hint) => Ok(hint.clone()),
69            None => {
70                let market_address = self
71                    .client
72                    .find_market_address(&self.store, &self.market_token);
73                let market = self.client.market(&market_address).await?;
74                let hint = UpdateAdlHint::from_market(self.client, &market).await?;
75                self.hint = Some(hint.clone());
76                Ok(hint)
77            }
78        }
79    }
80
81    /// Build [`TransactionBuilder`] for auto-delevearaging the position.
82    pub async fn build_txns(&mut self) -> crate::Result<Vec<TransactionBuilder<'a, C>>> {
83        let hint = self.prepare_hint().await?;
84        let feeds = self
85            .feeds_parser
86            .parse(hint.feeds())
87            .collect::<Result<Vec<_>, _>>()?;
88
89        let mut txns = vec![];
90
91        let sides = self
92            .for_long
93            .then_some(true)
94            .into_iter()
95            .chain(self.for_short.then_some(false));
96
97        for is_long in sides {
98            let rpc = self
99                .client
100                .store_transaction()
101                .accounts(fix_optional_account_metas(
102                    gmsol_store::accounts::UpdateAdlState {
103                        authority: self.client.payer(),
104                        store: self.store,
105                        token_map: hint.token_map,
106                        oracle: self.oracle,
107                        market: self
108                            .client
109                            .find_market_address(&self.store, &self.market_token),
110                        chainlink_program: None,
111                    },
112                    &crate::program_ids::DEFAULT_GMSOL_STORE_ID,
113                    self.client.store_program_id(),
114                ))
115                .anchor_args(gmsol_store::instruction::UpdateAdlState { is_long })
116                .accounts(feeds.clone())
117                .lookup_tables(self.alts.clone());
118
119            txns.push(rpc);
120        }
121
122        Ok(txns)
123    }
124}
125
126/// Hint for `update_adl_state`.
127#[derive(Clone)]
128pub struct UpdateAdlHint {
129    token_map: Pubkey,
130    tokens_with_feed: TokensWithFeed,
131}
132
133impl UpdateAdlHint {
134    async fn from_market<C: Deref<Target = impl Signer> + Clone>(
135        client: &crate::Client<C>,
136        market: &Market,
137    ) -> crate::Result<Self> {
138        use gmsol_store::states::common::token_with_feeds::token_records;
139
140        let store_address = market.store;
141        let token_map_address = client
142            .authorized_token_map_address(&store_address)
143            .await?
144            .ok_or(crate::Error::invalid_argument(
145                "token map is not configurated for the store",
146            ))?;
147        let token_map = client.token_map(&token_map_address).await?;
148        let meta = market.meta();
149
150        let records = token_records(
151            &token_map,
152            &[
153                meta.index_token_mint,
154                meta.long_token_mint,
155                meta.short_token_mint,
156            ]
157            .into(),
158        )?;
159        let tokens_with_feed = TokensWithFeed::try_from_records(records)?;
160
161        Ok(Self {
162            token_map: token_map_address,
163            tokens_with_feed,
164        })
165    }
166
167    /// Get feeds.
168    pub fn feeds(&self) -> &TokensWithFeed {
169        &self.tokens_with_feed
170    }
171}
172
173impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
174    for UpdateAdlBuilder<'a, C>
175{
176    async fn build_with_options(
177        &mut self,
178        options: BundleOptions,
179    ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
180        let mut bundle = self.client.bundle_with_options(options);
181
182        bundle.push_many(
183            self.build_txns()
184                .await
185                .map_err(gmsol_solana_utils::Error::custom)?,
186            false,
187        )?;
188
189        Ok(bundle)
190    }
191}
192
193impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer for UpdateAdlBuilder<'_, C> {
194    async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
195        let hint = self.prepare_hint().await?;
196        Ok(FeedIds::new(self.store, hint.tokens_with_feed))
197    }
198
199    fn process_feeds(
200        &mut self,
201        provider: PriceProviderKind,
202        map: FeedAddressMap,
203    ) -> crate::Result<()> {
204        self.feeds_parser
205            .insert_pull_oracle_feed_parser(provider, map);
206        Ok(())
207    }
208}
209
210impl<C> SetExecutionFee for UpdateAdlBuilder<'_, C> {
211    fn is_execution_fee_estimation_required(&self) -> bool {
212        false
213    }
214
215    fn set_execution_fee(&mut self, _lamports: u64) -> &mut Self {
216        self
217    }
218}