1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, TokenAccount};
3use typed_builder::TypedBuilder;
4
5use crate::{
6 events::EventEmitter,
7 states::{
8 common::{
9 action::{Action, ActionParams},
10 swap::SwapActionParamsExt,
11 },
12 market::revertible::Revertible,
13 withdrawal::Withdrawal,
14 Market, NonceBytes, Oracle, Store, ValidateOracleTime,
15 },
16 CoreError, CoreResult,
17};
18
19use super::market::RevertibleLiquidityMarketOperation;
20
21#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
23pub struct CreateWithdrawalParams {
24 pub execution_lamports: u64,
26 pub long_token_swap_path_length: u8,
28 pub short_token_swap_path_length: u8,
30 pub market_token_amount: u64,
32 pub min_long_token_amount: u64,
34 pub min_short_token_amount: u64,
36 pub should_unwrap_native_token: bool,
38}
39
40impl ActionParams for CreateWithdrawalParams {
41 fn execution_lamports(&self) -> u64 {
42 self.execution_lamports
43 }
44}
45
46#[derive(TypedBuilder)]
48pub(crate) struct CreateWithdrawalOperation<'a, 'info> {
49 withdrawal: AccountLoader<'info, Withdrawal>,
50 market: AccountLoader<'info, Market>,
51 store: AccountLoader<'info, Store>,
52 owner: &'a AccountInfo<'info>,
53 receiver: &'a AccountInfo<'info>,
54 nonce: &'a NonceBytes,
55 bump: u8,
56 final_long_token: &'a Account<'info, TokenAccount>,
57 final_short_token: &'a Account<'info, TokenAccount>,
58 market_token: &'a Account<'info, TokenAccount>,
59 params: &'a CreateWithdrawalParams,
60 swap_paths: &'info [AccountInfo<'info>],
61}
62
63impl CreateWithdrawalOperation<'_, '_> {
64 pub(crate) fn execute(self) -> Result<()> {
66 self.market.load()?.validate(&self.store.key())?;
67 self.validate_params_excluding_swap()?;
68
69 let Self {
70 withdrawal,
71 market,
72 store,
73 owner,
74 receiver,
75 nonce,
76 bump,
77 final_long_token,
78 final_short_token,
79 market_token,
80 params,
81 swap_paths,
82 } = self;
83
84 let id = market.load_mut()?.indexer_mut().next_withdrawal_id()?;
85
86 let mut withdrawal = withdrawal.load_init()?;
87
88 withdrawal.header.init(
90 id,
91 store.key(),
92 market.key(),
93 owner.key(),
94 receiver.key(),
95 *nonce,
96 bump,
97 params.execution_lamports,
98 params.should_unwrap_native_token,
99 )?;
100
101 withdrawal.tokens.market_token.init(market_token);
103 withdrawal.tokens.final_long_token.init(final_long_token);
104 withdrawal.tokens.final_short_token.init(final_short_token);
105
106 withdrawal.params.market_token_amount = params.market_token_amount;
108 withdrawal.params.min_long_token_amount = params.min_long_token_amount;
109 withdrawal.params.min_short_token_amount = params.min_short_token_amount;
110
111 let market = market.load()?;
113 let meta = market.meta();
114 withdrawal.swap.validate_and_init(
115 &*market,
116 params.long_token_swap_path_length,
117 params.short_token_swap_path_length,
118 swap_paths,
119 &store.key(),
120 (&meta.long_token_mint, &meta.short_token_mint),
121 (&final_long_token.mint, &final_short_token.mint),
122 )?;
123
124 Ok(())
125 }
126
127 fn validate_params_excluding_swap(&self) -> Result<()> {
128 let params = &self.params;
129 require!(params.market_token_amount != 0, CoreError::EmptyWithdrawal);
130 require_gte!(
131 self.market_token.amount,
132 params.market_token_amount,
133 CoreError::NotEnoughTokenAmount,
134 );
135
136 Ok(())
137 }
138}
139
140#[derive(TypedBuilder)]
142pub(crate) struct ExecuteWithdrawalOperation<'a, 'info> {
143 store: &'a AccountLoader<'info, Store>,
144 market: &'a AccountLoader<'info, Market>,
145 market_token_mint: &'a mut Account<'info, Mint>,
146 market_token_vault: AccountInfo<'info>,
147 withdrawal: &'a AccountLoader<'info, Withdrawal>,
148 oracle: &'a Oracle,
149 remaining_accounts: &'info [AccountInfo<'info>],
150 throw_on_execution_error: bool,
151 token_program: AccountInfo<'info>,
152 #[builder(setter(into))]
153 event_emitter: EventEmitter<'a, 'info>,
154}
155
156impl ExecuteWithdrawalOperation<'_, '_> {
157 pub(crate) fn execute(self) -> Result<Option<(u64, u64)>> {
158 let throw_on_execution_error = self.throw_on_execution_error;
159 match self.validate_oracle() {
160 Ok(()) => {}
161 Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
162 msg!(
163 "Withdrawal expired at {}",
164 self.oracle_updated_before()
165 .ok()
166 .flatten()
167 .expect("must have an expiration time"),
168 );
169 return Ok(None);
170 }
171 Err(err) => {
172 return Err(error!(err));
173 }
174 }
175 match self.perform_withdrawal() {
176 Ok(res) => Ok(Some(res)),
177 Err(err) if !throw_on_execution_error => {
178 msg!("Execute withdrawal error: {}", err);
179 Ok(None)
180 }
181 Err(err) => Err(err),
182 }
183 }
184
185 fn validate_oracle(&self) -> CoreResult<()> {
186 self.oracle.validate_time(self)
187 }
188
189 #[inline(never)]
190 fn perform_withdrawal(self) -> Result<(u64, u64)> {
191 self.market.load()?.validate(&self.store.key())?;
192
193 let withdrawal = self.withdrawal.load()?;
194
195 let mut market = RevertibleLiquidityMarketOperation::new(
196 self.store,
197 self.oracle,
198 self.market,
199 self.market_token_mint,
200 self.token_program,
201 Some(&withdrawal.swap),
202 self.remaining_accounts,
203 self.event_emitter,
204 )?;
205
206 let executed = market.op()?.unchecked_withdraw(
207 &self.market_token_vault,
208 &withdrawal.params,
209 (
210 withdrawal.tokens.final_long_token(),
211 withdrawal.tokens.final_short_token(),
212 ),
213 None,
214 )?;
215
216 let final_output_amounts = executed.output;
217
218 executed.commit();
219
220 Ok(final_output_amounts)
221 }
222}
223
224impl ValidateOracleTime for ExecuteWithdrawalOperation<'_, '_> {
225 fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
226 Ok(Some(
227 self.withdrawal
228 .load()
229 .map_err(|_| CoreError::LoadAccountError)?
230 .header()
231 .updated_at,
232 ))
233 }
234
235 fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
236 let ts = self
237 .store
238 .load()
239 .map_err(|_| CoreError::LoadAccountError)?
240 .request_expiration_at(
241 self.withdrawal
242 .load()
243 .map_err(|_| CoreError::LoadAccountError)?
244 .header()
245 .updated_at,
246 )?;
247 Ok(Some(ts))
248 }
249
250 fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
251 Ok(Some(
252 self.withdrawal
253 .load()
254 .map_err(|_| CoreError::LoadAccountError)?
255 .header()
256 .updated_at_slot,
257 ))
258 }
259}