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#[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}
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 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}