Skip to main content

gmsol_store/ops/
shift.rs

1use std::{borrow::BorrowMut, cell::RefMut};
2
3use anchor_lang::prelude::*;
4use anchor_spl::token::{Mint, TokenAccount};
5use gmsol_utils::InitSpace;
6use typed_builder::TypedBuilder;
7
8use crate::{
9    events::EventEmitter,
10    states::{
11        common::action::{Action, ActionExt, ActionParams},
12        market::revertible::Revertible,
13        Market, NonceBytes, Oracle, Shift, Store, ValidateOracleTime,
14    },
15    CoreError, CoreResult,
16};
17
18use super::market::{RemainingAccountsForMarket, RevertibleLiquidityMarketOperation};
19
20/// Create Shift Params.
21#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
22pub struct CreateShiftParams {
23    /// Execution fee in lamports.
24    pub execution_lamports: u64,
25    /// From market token amount.
26    pub from_market_token_amount: u64,
27    /// The minimum acceptable to market token amount to receive.
28    pub min_to_market_token_amount: u64,
29}
30
31impl ActionParams for CreateShiftParams {
32    fn execution_lamports(&self) -> u64 {
33        self.execution_lamports
34    }
35}
36
37/// Operation for creating a shift.
38#[derive(TypedBuilder)]
39pub struct CreateShiftOperation<'a, 'info, T>
40where
41    T: anchor_lang::ZeroCopy + anchor_lang::Owner,
42{
43    store: &'a AccountLoader<'info, Store>,
44    owner: &'a AccountInfo<'info>,
45    receiver: &'a AccountInfo<'info>,
46    shift: &'a AccountLoader<'info, T>,
47    from_market: &'a AccountLoader<'info, Market>,
48    from_market_token_account: &'a Account<'info, TokenAccount>,
49    to_market: &'a AccountLoader<'info, Market>,
50    to_market_token_account: &'a Account<'info, TokenAccount>,
51    nonce: &'a NonceBytes,
52    bump: u8,
53    params: &'a CreateShiftParams,
54}
55
56impl<T> CreateShiftOperation<'_, '_, T>
57where
58    T: anchor_lang::ZeroCopy + anchor_lang::Owner + Action + InitSpace,
59    T: BorrowMut<Shift>,
60{
61    pub(crate) fn execute(self) -> Result<()> {
62        self.validate_markets()?;
63        self.validate_params()?;
64
65        let id = self.from_market.load_mut()?.indexer_mut().next_shift_id()?;
66
67        let mut shift = RefMut::map(self.shift.load_init()?, |shift| shift.borrow_mut());
68
69        // Initialize the header.
70        shift.header.init(
71            id,
72            self.store.key(),
73            self.from_market.key(),
74            self.owner.key(),
75            self.receiver.key(),
76            *self.nonce,
77            self.bump,
78            self.params.execution_lamports,
79            false,
80        )?;
81
82        // Initialize tokens.
83        shift
84            .tokens
85            .from_market_token
86            .init(self.from_market_token_account);
87        shift
88            .tokens
89            .to_market_token
90            .init(self.to_market_token_account);
91        {
92            let market = self.from_market.load()?;
93            shift.tokens.long_token = market.meta().long_token_mint;
94            shift.tokens.short_token = market.meta().short_token_mint;
95        }
96
97        // Initialize params.
98        shift.params.from_market_token_amount = self.params.from_market_token_amount;
99        shift.params.min_to_market_token_amount = self.params.min_to_market_token_amount;
100
101        Ok(())
102    }
103
104    fn validate_markets(&self) -> Result<()> {
105        require!(
106            self.from_market.key() != self.to_market.key(),
107            CoreError::InvalidShiftMarkets,
108        );
109
110        let from_market = self.from_market.load()?;
111        let to_market = self.to_market.load()?;
112
113        let store = &self.store.key();
114        from_market.validate(store)?;
115        to_market.validate(store)?;
116
117        from_market.validate_shiftable(&to_market)?;
118
119        require_keys_eq!(
120            from_market.meta().market_token_mint,
121            self.from_market_token_account.mint,
122            CoreError::MarketTokenMintMismatched,
123        );
124
125        require_keys_eq!(
126            to_market.meta().market_token_mint,
127            self.to_market_token_account.mint,
128            CoreError::MarketTokenMintMismatched,
129        );
130        Ok(())
131    }
132
133    fn validate_params(&self) -> Result<()> {
134        let params = &self.params;
135
136        require!(params.from_market_token_amount != 0, CoreError::EmptyShift);
137        require_gte!(
138            self.from_market_token_account.amount,
139            params.from_market_token_amount,
140            CoreError::NotEnoughTokenAmount
141        );
142
143        ActionExt::validate_balance(self.shift, params.execution_lamports)?;
144        Ok(())
145    }
146}
147
148/// Operation for executing a shift.
149#[derive(TypedBuilder)]
150pub struct ExecuteShiftOperation<'a, 'info> {
151    store: &'a AccountLoader<'info, Store>,
152    oracle: &'a Oracle,
153    shift: &'a AccountLoader<'info, Shift>,
154    from_market: &'a AccountLoader<'info, Market>,
155    from_market_token_mint: &'a mut Account<'info, Mint>,
156    from_market_token_vault: AccountInfo<'info>,
157    to_market: &'a AccountLoader<'info, Market>,
158    to_market_token_mint: &'a mut Account<'info, Mint>,
159    to_market_token_account: AccountInfo<'info>,
160    throw_on_execution_error: bool,
161    token_program: AccountInfo<'info>,
162    #[builder(setter(into))]
163    event_emitter: EventEmitter<'a, 'info>,
164    remaining_accounts: &'info [AccountInfo<'info>],
165}
166
167impl ExecuteShiftOperation<'_, '_> {
168    pub(crate) fn execute(self) -> Result<bool> {
169        let throw_on_execution_error = self.throw_on_execution_error;
170
171        match self.validate_oracle() {
172            Ok(()) => {}
173            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
174                msg!(
175                    "shift expired at {}",
176                    self.oracle_updated_before()
177                        .ok()
178                        .flatten()
179                        .expect("must have an expiration time"),
180                );
181                return Ok(false);
182            }
183            Err(err) => {
184                return Err(error!(err));
185            }
186        }
187        match self.perform_shift() {
188            Ok(()) => Ok(true),
189            Err(err) if !throw_on_execution_error => {
190                msg!("Execute shift error: {}", err);
191                Ok(false)
192            }
193            Err(err) => Err(err),
194        }
195    }
196
197    fn validate_oracle(&self) -> CoreResult<()> {
198        self.oracle.validate_time(self)
199    }
200
201    fn validate_markets_and_shift(&self) -> Result<()> {
202        require!(
203            self.from_market.key() != self.to_market.key(),
204            CoreError::Internal
205        );
206
207        let from_market = self.from_market.load()?;
208        let to_market = self.to_market.load()?;
209
210        from_market.validate(&self.store.key())?;
211        to_market.validate(&self.store.key())?;
212
213        from_market.validate_shiftable(&to_market)?;
214
215        Ok(())
216    }
217
218    #[inline(never)]
219    fn perform_shift(self) -> Result<()> {
220        self.validate_markets_and_shift()?;
221
222        let shift = self.shift.load()?;
223        let remaining_accounts = RemainingAccountsForMarket::new(
224            self.remaining_accounts,
225            self.from_market_token_mint.key(),
226            None,
227        )?;
228        let virtual_inventories = remaining_accounts.load_virtual_inventories()?;
229
230        let mut from_market = RevertibleLiquidityMarketOperation::new(
231            self.store,
232            self.oracle,
233            self.from_market,
234            self.from_market_token_mint,
235            self.token_program.clone(),
236            None,
237            &[],
238            &virtual_inventories,
239            self.event_emitter,
240        )?;
241
242        let mut to_market = RevertibleLiquidityMarketOperation::new(
243            self.store,
244            self.oracle,
245            self.to_market,
246            self.to_market_token_mint,
247            self.token_program,
248            None,
249            &[],
250            &virtual_inventories,
251            self.event_emitter,
252        )?;
253
254        let from_market = from_market.op()?;
255        let to_market = to_market.op()?;
256
257        let (from_market, to_market, _) = from_market.unchecked_shift(
258            to_market,
259            &shift.header().receiver(),
260            &shift.params,
261            &self.from_market_token_vault,
262            &self.to_market_token_account,
263        )?;
264
265        // Commit the changes.
266        from_market.commit();
267        to_market.commit();
268        virtual_inventories.commit();
269
270        Ok(())
271    }
272}
273
274impl ValidateOracleTime for ExecuteShiftOperation<'_, '_> {
275    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
276        Ok(Some(
277            self.shift
278                .load()
279                .map_err(|_| CoreError::LoadAccountError)?
280                .header()
281                .updated_at,
282        ))
283    }
284
285    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
286        let ts = self
287            .store
288            .load()
289            .map_err(|_| CoreError::LoadAccountError)?
290            .request_expiration_at(
291                self.shift
292                    .load()
293                    .map_err(|_| CoreError::LoadAccountError)?
294                    .header()
295                    .updated_at,
296            )?;
297        Ok(Some(ts))
298    }
299
300    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
301        Ok(Some(
302            self.shift
303                .load()
304                .map_err(|_| CoreError::LoadAccountError)?
305                .header()
306                .updated_at_slot,
307        ))
308    }
309}