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#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
22pub struct CreateShiftParams {
23 pub execution_lamports: u64,
25 pub from_market_token_amount: u64,
27 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#[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 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 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 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#[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 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}