gmsol/store/glv/
shift.rs

1use std::{collections::HashMap, ops::Deref};
2
3use anchor_client::{
4    anchor_lang::system_program,
5    solana_sdk::{address_lookup_table::AddressLookupTableAccount, pubkey::Pubkey, signer::Signer},
6};
7use anchor_spl::associated_token::get_associated_token_address;
8use gmsol_solana_utils::{
9    bundle_builder::{BundleBuilder, BundleOptions},
10    transaction_builder::TransactionBuilder,
11};
12use gmsol_store::{
13    accounts, instruction,
14    ops::shift::CreateShiftParams,
15    states::{
16        common::{action::Action, TokensWithFeed},
17        glv::GlvShift,
18        HasMarketMeta, NonceBytes, PriceProviderKind, Shift, Store, TokenMapAccess,
19    },
20};
21use gmsol_utils::market::ordered_tokens;
22
23use crate::{
24    exchange::generate_nonce,
25    store::utils::FeedsParser,
26    utils::{
27        builder::{
28            FeedAddressMap, FeedIds, MakeBundleBuilder, PullOraclePriceConsumer, SetExecutionFee,
29        },
30        fix_optional_account_metas, ZeroCopy,
31    },
32};
33
34#[cfg(feature = "pyth-pull-oracle")]
35use crate::pyth::pull_oracle::Prices;
36
37use super::GlvOps;
38
39/// Create Shift Builder.
40pub struct CreateGlvShiftBuilder<'a, C> {
41    client: &'a crate::Client<C>,
42    store: Pubkey,
43    glv_token: Pubkey,
44    from_market_token: Pubkey,
45    to_market_token: Pubkey,
46    execution_fee: u64,
47    amount: u64,
48    min_to_market_token_amount: u64,
49    nonce: Option<NonceBytes>,
50}
51
52impl<'a, C: Deref<Target = impl Signer> + Clone> CreateGlvShiftBuilder<'a, C> {
53    pub(super) fn new(
54        client: &'a crate::Client<C>,
55        store: &Pubkey,
56        glv_token: &Pubkey,
57        from_market_token: &Pubkey,
58        to_market_token: &Pubkey,
59        amount: u64,
60    ) -> Self {
61        Self {
62            client,
63            store: *store,
64            glv_token: *glv_token,
65            from_market_token: *from_market_token,
66            to_market_token: *to_market_token,
67            execution_fee: Shift::MIN_EXECUTION_LAMPORTS,
68            amount,
69            min_to_market_token_amount: 0,
70            nonce: None,
71        }
72    }
73
74    /// Set the nonce.
75    pub fn nonce(&mut self, nonce: NonceBytes) -> &mut Self {
76        self.nonce = Some(nonce);
77        self
78    }
79
80    /// Set exectuion fee allowed to use.
81    pub fn execution_fee(&mut self, amount: u64) -> &mut Self {
82        self.execution_fee = amount;
83        self
84    }
85
86    /// Set min to market token amount.
87    pub fn min_to_market_token_amount(&mut self, amount: u64) -> &mut Self {
88        self.min_to_market_token_amount = amount;
89        self
90    }
91
92    fn get_create_shift_params(&self) -> CreateShiftParams {
93        CreateShiftParams {
94            execution_lamports: self.execution_fee,
95            from_market_token_amount: self.amount,
96            min_to_market_token_amount: self.min_to_market_token_amount,
97        }
98    }
99
100    /// Build a [`TransactionBuilder`] to create shift account and return the address of the shift account to create.
101    pub fn build_with_address(&self) -> crate::Result<(TransactionBuilder<'a, C>, Pubkey)> {
102        let authority = self.client.payer();
103        let nonce = self.nonce.unwrap_or_else(generate_nonce);
104        let glv = self.client.find_glv_address(&self.glv_token);
105        let glv_shift = self
106            .client
107            .find_shift_address(&self.store, &authority, &nonce);
108
109        let token_program_id = anchor_spl::token::ID;
110
111        let from_market = self
112            .client
113            .find_market_address(&self.store, &self.from_market_token);
114        let to_market = self
115            .client
116            .find_market_address(&self.store, &self.to_market_token);
117
118        let from_market_token_vault = get_associated_token_address(&glv, &self.from_market_token);
119        let to_market_token_vault = get_associated_token_address(&glv, &self.to_market_token);
120
121        let rpc = self
122            .client
123            .store_transaction()
124            .anchor_accounts(accounts::CreateGlvShift {
125                authority,
126                store: self.store,
127                from_market,
128                to_market,
129                glv_shift,
130                from_market_token: self.from_market_token,
131                to_market_token: self.to_market_token,
132                from_market_token_vault,
133                to_market_token_vault,
134                system_program: system_program::ID,
135                token_program: token_program_id,
136                associated_token_program: anchor_spl::associated_token::ID,
137                glv,
138            })
139            .anchor_args(instruction::CreateGlvShift {
140                nonce,
141                params: self.get_create_shift_params(),
142            });
143
144        Ok((rpc, glv_shift))
145    }
146}
147
148/// Close GLV Shift Builder.
149pub struct CloseGlvShiftBuilder<'a, C> {
150    client: &'a crate::Client<C>,
151    glv_shift: Pubkey,
152    reason: String,
153    hint: Option<CloseGlvShiftHint>,
154}
155
156/// Hint for `close_shift` instruction.
157#[derive(Clone)]
158pub struct CloseGlvShiftHint {
159    store: Pubkey,
160    owner: Pubkey,
161    funder: Pubkey,
162    from_market_token: Pubkey,
163    to_market_token: Pubkey,
164}
165
166impl CloseGlvShiftHint {
167    /// Create hint for `close_shift` instruction.
168    pub fn new(glv_shift: &GlvShift) -> crate::Result<Self> {
169        let tokens = glv_shift.tokens();
170        Ok(Self {
171            store: *glv_shift.header().store(),
172            owner: *glv_shift.header().owner(),
173            funder: *glv_shift.funder(),
174            from_market_token: tokens.from_market_token(),
175            to_market_token: tokens.to_market_token(),
176        })
177    }
178}
179
180impl<'a, C: Deref<Target = impl Signer> + Clone> CloseGlvShiftBuilder<'a, C> {
181    pub(super) fn new(client: &'a crate::Client<C>, shift: &Pubkey) -> Self {
182        Self {
183            client,
184            glv_shift: *shift,
185            hint: None,
186            reason: String::from("cancelled"),
187        }
188    }
189
190    /// Set hint.
191    pub fn hint(&mut self, hint: CloseGlvShiftHint) -> &mut Self {
192        self.hint = Some(hint);
193        self
194    }
195
196    /// Set reason.
197    pub fn reason(&mut self, reason: impl ToString) -> &mut Self {
198        self.reason = reason.to_string();
199        self
200    }
201
202    /// Prepare hint if needed
203    pub async fn prepare_hint(&mut self) -> crate::Result<CloseGlvShiftHint> {
204        let hint = match &self.hint {
205            Some(hint) => hint.clone(),
206            None => {
207                let shift = self
208                    .client
209                    .account::<ZeroCopy<_>>(&self.glv_shift)
210                    .await?
211                    .ok_or(crate::Error::NotFound)?;
212                let hint = CloseGlvShiftHint::new(&shift.0)?;
213                self.hint = Some(hint.clone());
214                hint
215            }
216        };
217
218        Ok(hint)
219    }
220
221    /// Build a [`TransactionBuilder`] to close shift account.
222    pub async fn build(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
223        let hint = self.prepare_hint().await?;
224        let authority = self.client.payer();
225        let rpc = self
226            .client
227            .store_transaction()
228            .anchor_accounts(accounts::CloseGlvShift {
229                authority,
230                funder: hint.funder,
231                store: hint.store,
232                store_wallet: self.client.find_store_wallet_address(&hint.store),
233                glv: hint.owner,
234                glv_shift: self.glv_shift,
235                from_market_token: hint.from_market_token,
236                to_market_token: hint.to_market_token,
237                system_program: system_program::ID,
238                token_program: anchor_spl::token::ID,
239                associated_token_program: anchor_spl::associated_token::ID,
240                event_authority: self.client.store_event_authority(),
241                program: *self.client.store_program_id(),
242            })
243            .anchor_args(instruction::CloseGlvShift {
244                reason: self.reason.clone(),
245            });
246
247        Ok(rpc)
248    }
249}
250
251/// Execute GLV Shift Instruction Builder.
252pub struct ExecuteGlvShiftBuilder<'a, C> {
253    client: &'a crate::Client<C>,
254    shift: Pubkey,
255    execution_fee: u64,
256    cancel_on_execution_error: bool,
257    oracle: Pubkey,
258    hint: Option<ExecuteGlvShiftHint>,
259    close: bool,
260    feeds_parser: FeedsParser,
261    alts: HashMap<Pubkey, Vec<Pubkey>>,
262}
263
264/// Hint for `execute_shift` instruction.
265#[derive(Clone)]
266pub struct ExecuteGlvShiftHint {
267    store: Pubkey,
268    token_map: Pubkey,
269    owner: Pubkey,
270    funder: Pubkey,
271    from_market_token: Pubkey,
272    to_market_token: Pubkey,
273    /// Feeds.
274    pub feeds: TokensWithFeed,
275}
276
277impl ExecuteGlvShiftHint {
278    /// Create hint for `execute_shift` instruction.
279    pub fn new(
280        glv_shift: &GlvShift,
281        store: &Store,
282        map: &impl TokenMapAccess,
283        from_market: &impl HasMarketMeta,
284        to_market: &impl HasMarketMeta,
285    ) -> crate::Result<Self> {
286        use gmsol_store::states::common::token_with_feeds::token_records;
287
288        let token_infos = glv_shift.tokens();
289
290        let ordered_tokens = ordered_tokens(from_market, to_market);
291        let token_records = token_records(map, &ordered_tokens)?;
292        let feeds = TokensWithFeed::try_from_records(token_records)?;
293
294        Ok(Self {
295            store: *glv_shift.header().store(),
296            owner: *glv_shift.header().owner(),
297            funder: *glv_shift.funder(),
298            from_market_token: token_infos.from_market_token(),
299            to_market_token: token_infos.to_market_token(),
300            token_map: *store.token_map().ok_or(crate::Error::NotFound)?,
301            feeds,
302        })
303    }
304}
305
306impl<'a, C: Deref<Target = impl Signer> + Clone> ExecuteGlvShiftBuilder<'a, C> {
307    pub(super) fn new(client: &'a crate::Client<C>, oracle: &Pubkey, shift: &Pubkey) -> Self {
308        Self {
309            client,
310            shift: *shift,
311            hint: None,
312            execution_fee: 0,
313            cancel_on_execution_error: true,
314            oracle: *oracle,
315            close: true,
316            feeds_parser: Default::default(),
317            alts: Default::default(),
318        }
319    }
320
321    /// Set hint.
322    pub fn hint(&mut self, hint: ExecuteGlvShiftHint) -> &mut Self {
323        self.hint = Some(hint);
324        self
325    }
326
327    /// Set whether to cancel the shift account on execution error.
328    pub fn cancel_on_execution_error(&mut self, cancel: bool) -> &mut Self {
329        self.cancel_on_execution_error = cancel;
330        self
331    }
332
333    /// Set whether to close shift account after execution.
334    pub fn close(&mut self, close: bool) -> &mut Self {
335        self.close = close;
336        self
337    }
338
339    /// Insert an Address Lookup Table.
340    pub fn add_alt(&mut self, account: AddressLookupTableAccount) -> &mut Self {
341        self.alts.insert(account.key, account.addresses);
342        self
343    }
344
345    /// Parse feeds with the given price udpates map.
346    #[cfg(feature = "pyth-pull-oracle")]
347    pub fn parse_with_pyth_price_updates(&mut self, price_updates: Prices) -> &mut Self {
348        self.feeds_parser.with_pyth_price_updates(price_updates);
349        self
350    }
351
352    /// Prepare hint.
353    pub async fn prepare_hint(&mut self) -> crate::Result<ExecuteGlvShiftHint> {
354        let hint = match &self.hint {
355            Some(hint) => hint.clone(),
356            None => {
357                let shift = self
358                    .client
359                    .account::<ZeroCopy<GlvShift>>(&self.shift)
360                    .await?
361                    .ok_or(crate::Error::NotFound)?;
362                let store = self.client.store(shift.0.header().store()).await?;
363                let token_map_address = store
364                    .token_map()
365                    .ok_or(crate::Error::invalid_argument("token map is not set"))?;
366                let token_map = self.client.token_map(token_map_address).await?;
367                let from_market_token = shift.0.tokens().from_market_token();
368                let to_market_token = shift.0.tokens().to_market_token();
369                let from_market = self
370                    .client
371                    .find_market_address(shift.0.header().store(), &from_market_token);
372                let from_market = self.client.market(&from_market).await?;
373                let to_market = self
374                    .client
375                    .find_market_address(shift.0.header().store(), &to_market_token);
376                let to_market = self.client.market(&to_market).await?;
377                let hint = ExecuteGlvShiftHint::new(
378                    &shift.0,
379                    &store,
380                    &token_map,
381                    &*from_market,
382                    &*to_market,
383                )?;
384                self.hint = Some(hint.clone());
385                hint
386            }
387        };
388
389        Ok(hint)
390    }
391
392    /// Build a [`TransactionBuilder`] for `execute_shift` instruction.
393    async fn build_rpc(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
394        let hint = self.prepare_hint().await?;
395        let authority = self.client.payer();
396
397        let glv = hint.owner;
398
399        let from_market = self
400            .client
401            .find_market_address(&hint.store, &hint.from_market_token);
402        let to_market = self
403            .client
404            .find_market_address(&hint.store, &hint.to_market_token);
405        let from_market_token_vault = self
406            .client
407            .find_market_vault_address(&hint.store, &hint.from_market_token);
408
409        let from_market_token_glv_vault =
410            get_associated_token_address(&glv, &hint.from_market_token);
411        let to_market_token_glv_vault = get_associated_token_address(&glv, &hint.to_market_token);
412
413        let feeds = self.feeds_parser.parse_and_sort_by_tokens(&hint.feeds)?;
414
415        let mut rpc = self
416            .client
417            .store_transaction()
418            .accounts(fix_optional_account_metas(
419                accounts::ExecuteGlvShift {
420                    authority,
421                    store: hint.store,
422                    token_map: hint.token_map,
423                    oracle: self.oracle,
424                    glv,
425                    from_market,
426                    to_market,
427                    glv_shift: self.shift,
428                    from_market_token: hint.from_market_token,
429                    to_market_token: hint.to_market_token,
430                    from_market_token_glv_vault,
431                    to_market_token_glv_vault,
432                    from_market_token_vault,
433                    token_program: anchor_spl::token::ID,
434                    chainlink_program: None,
435                    event_authority: self.client.store_event_authority(),
436                    program: *self.client.store_program_id(),
437                },
438                &crate::program_ids::DEFAULT_GMSOL_STORE_ID,
439                self.client.store_program_id(),
440            ))
441            .anchor_args(instruction::ExecuteGlvShift {
442                execution_lamports: self.execution_fee,
443                throw_on_execution_error: !self.cancel_on_execution_error,
444            })
445            .accounts(feeds)
446            .lookup_tables(self.alts.clone());
447
448        if self.close {
449            let close = self
450                .client
451                .close_glv_shift(&self.shift)
452                .hint(CloseGlvShiftHint {
453                    store: hint.store,
454                    owner: hint.owner,
455                    funder: hint.funder,
456                    from_market_token: hint.from_market_token,
457                    to_market_token: hint.to_market_token,
458                })
459                .reason("executed")
460                .build()
461                .await?;
462            rpc = rpc.merge(close);
463        }
464
465        Ok(rpc)
466    }
467}
468
469#[cfg(feature = "pyth-pull-oracle")]
470mod pyth {
471    use crate::pyth::{pull_oracle::ExecuteWithPythPrices, PythPullOracleContext};
472
473    use super::*;
474
475    impl<'a, C: Deref<Target = impl Signer> + Clone> ExecuteWithPythPrices<'a, C>
476        for ExecuteGlvShiftBuilder<'a, C>
477    {
478        fn set_execution_fee(&mut self, lamports: u64) {
479            SetExecutionFee::set_execution_fee(self, lamports);
480        }
481
482        async fn context(&mut self) -> crate::Result<PythPullOracleContext> {
483            let hint = self.prepare_hint().await?;
484            let ctx = PythPullOracleContext::try_from_feeds(&hint.feeds)?;
485            Ok(ctx)
486        }
487
488        async fn build_rpc_with_price_updates(
489            &mut self,
490            price_updates: Prices,
491        ) -> crate::Result<Vec<TransactionBuilder<'a, C, ()>>> {
492            let txn = self
493                .parse_with_pyth_price_updates(price_updates)
494                .build()
495                .await?;
496            Ok(txn.into_builders())
497        }
498    }
499}
500
501impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
502    for ExecuteGlvShiftBuilder<'a, C>
503{
504    async fn build_with_options(
505        &mut self,
506        options: BundleOptions,
507    ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
508        let mut tx = self.client.bundle_with_options(options);
509
510        tx.try_push(
511            self.build_rpc()
512                .await
513                .map_err(gmsol_solana_utils::Error::custom)?,
514        )?;
515
516        Ok(tx)
517    }
518}
519
520impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
521    for ExecuteGlvShiftBuilder<'_, C>
522{
523    async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
524        let hint = self.prepare_hint().await?;
525        Ok(FeedIds::new(hint.store, hint.feeds))
526    }
527
528    fn process_feeds(
529        &mut self,
530        provider: PriceProviderKind,
531        map: FeedAddressMap,
532    ) -> crate::Result<()> {
533        self.feeds_parser
534            .insert_pull_oracle_feed_parser(provider, map);
535        Ok(())
536    }
537}
538
539impl<C> SetExecutionFee for ExecuteGlvShiftBuilder<'_, C> {
540    fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
541        self.execution_fee = lamports;
542        self
543    }
544}