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::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}
165
166impl ExecuteShiftOperation<'_, '_> {
167    pub(crate) fn execute(self) -> Result<bool> {
168        let throw_on_execution_error = self.throw_on_execution_error;
169
170        match self.validate_oracle() {
171            Ok(()) => {}
172            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
173                msg!(
174                    "shift expired at {}",
175                    self.oracle_updated_before()
176                        .ok()
177                        .flatten()
178                        .expect("must have an expiration time"),
179                );
180                return Ok(false);
181            }
182            Err(err) => {
183                return Err(error!(err));
184            }
185        }
186        match self.perform_shift() {
187            Ok(()) => Ok(true),
188            Err(err) if !throw_on_execution_error => {
189                msg!("Execute shift error: {}", err);
190                Ok(false)
191            }
192            Err(err) => Err(err),
193        }
194    }
195
196    fn validate_oracle(&self) -> CoreResult<()> {
197        self.oracle.validate_time(self)
198    }
199
200    fn validate_markets_and_shift(&self) -> Result<()> {
201        require!(
202            self.from_market.key() != self.to_market.key(),
203            CoreError::Internal
204        );
205
206        let from_market = self.from_market.load()?;
207        let to_market = self.to_market.load()?;
208
209        from_market.validate(&self.store.key())?;
210        to_market.validate(&self.store.key())?;
211
212        from_market.validate_shiftable(&to_market)?;
213
214        Ok(())
215    }
216
217    #[inline(never)]
218    fn perform_shift(self) -> Result<()> {
219        self.validate_markets_and_shift()?;
220
221        let shift = self.shift.load()?;
222
223        let mut from_market = RevertibleLiquidityMarketOperation::new(
224            self.store,
225            self.oracle,
226            self.from_market,
227            self.from_market_token_mint,
228            self.token_program.clone(),
229            None,
230            &[],
231            self.event_emitter,
232        )?;
233
234        let mut to_market = RevertibleLiquidityMarketOperation::new(
235            self.store,
236            self.oracle,
237            self.to_market,
238            self.to_market_token_mint,
239            self.token_program,
240            None,
241            &[],
242            self.event_emitter,
243        )?;
244
245        let from_market = from_market.op()?;
246        let to_market = to_market.op()?;
247
248        let (from_market, to_market, _) = from_market.unchecked_shift(
249            to_market,
250            &shift.header().receiver(),
251            &shift.params,
252            &self.from_market_token_vault,
253            &self.to_market_token_account,
254        )?;
255
256        // Commit the changes.
257        from_market.commit();
258        to_market.commit();
259
260        Ok(())
261    }
262}
263
264impl ValidateOracleTime for ExecuteShiftOperation<'_, '_> {
265    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
266        Ok(Some(
267            self.shift
268                .load()
269                .map_err(|_| CoreError::LoadAccountError)?
270                .header()
271                .updated_at,
272        ))
273    }
274
275    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
276        let ts = self
277            .store
278            .load()
279            .map_err(|_| CoreError::LoadAccountError)?
280            .request_expiration_at(
281                self.shift
282                    .load()
283                    .map_err(|_| CoreError::LoadAccountError)?
284                    .header()
285                    .updated_at,
286            )?;
287        Ok(Some(ts))
288    }
289
290    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
291        Ok(Some(
292            self.shift
293                .load()
294                .map_err(|_| CoreError::LoadAccountError)?
295                .header()
296                .updated_at_slot,
297        ))
298    }
299}