1use anchor_lang::{prelude::*, ZeroCopy};
2use gmsol_callback::{cpi::on_updated, interface::ActionKind};
3use gmsol_utils::{
4 action::{ActionCallbackKind, ActionError, MAX_ACTION_FLAGS},
5 InitSpace,
6};
7
8use crate::{
9 events::Event,
10 states::{callback::CallbackAuthority, NonceBytes, Seed},
11 utils::pubkey::optional_address,
12 CoreError,
13};
14
15pub use gmsol_utils::action::{ActionFlag, ActionState};
16
17#[zero_copy]
19#[cfg_attr(feature = "debug", derive(Debug))]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct ActionHeader {
22 version: u8,
23 action_state: u8,
25 pub(crate) bump: u8,
27 flags: ActionFlagContainer,
28 callback_kind: u8,
29 callback_version: u8,
30 padding_0: [u8; 2],
31 pub id: u64,
33 pub store: Pubkey,
35 pub market: Pubkey,
37 pub owner: Pubkey,
39 pub nonce: [u8; 32],
41 pub(crate) max_execution_lamports: u64,
43 pub(crate) updated_at: i64,
45 pub(crate) updated_at_slot: u64,
47 pub(crate) creator: Pubkey,
49 rent_receiver: Pubkey,
51 receiver: Pubkey,
53 pub callback_program_id: Pubkey,
55 pub callback_shared_data: Pubkey,
57 pub callback_partitioned_data: Pubkey,
59 #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
60 reserved: [u8; 160],
61}
62
63impl Default for ActionHeader {
64 fn default() -> Self {
65 bytemuck::Zeroable::zeroed()
66 }
67}
68
69gmsol_utils::flags!(ActionFlag, MAX_ACTION_FLAGS, u8);
70
71impl ActionHeader {
72 pub fn action_state(&self) -> Result<ActionState> {
74 ActionState::try_from(self.action_state).map_err(|_| error!(CoreError::UnknownActionState))
75 }
76
77 fn set_action_state(&mut self, new_state: ActionState) {
78 self.action_state = new_state.into();
79 }
80
81 pub fn callback_kind(&self) -> Result<ActionCallbackKind> {
83 ActionCallbackKind::try_from(self.callback_kind).map_err(|_| error!(CoreError::Internal))
84 }
85
86 pub(crate) fn set_general_callback(
88 &mut self,
89 program_id: &Pubkey,
90 callback_version: u8,
91 shared_data: &Pubkey,
92 partitioned_data: &Pubkey,
93 ) -> Result<()> {
94 require_eq!(
95 self.callback_kind()?,
96 ActionCallbackKind::Disabled,
97 CoreError::PreconditionsAreNotMet
98 );
99 self.callback_version = callback_version;
100 self.callback_kind = ActionCallbackKind::General.into();
101 self.callback_program_id = *program_id;
102 self.callback_shared_data = *shared_data;
103 self.callback_partitioned_data = *partitioned_data;
104 Ok(())
105 }
106
107 pub(crate) fn validate_general_callback(
109 &self,
110 program_id: &Pubkey,
111 shared_data: &Pubkey,
112 partitioned_data: &Pubkey,
113 ) -> Result<()> {
114 require_eq!(
115 self.callback_kind()?,
116 ActionCallbackKind::General,
117 CoreError::InvalidArgument
118 );
119 require_keys_eq!(
120 *program_id,
121 self.callback_program_id,
122 CoreError::InvalidArgument
123 );
124 require_keys_eq!(
125 *shared_data,
126 self.callback_shared_data,
127 CoreError::InvalidArgument
128 );
129 require_keys_eq!(
130 *partitioned_data,
131 self.callback_partitioned_data,
132 CoreError::InvalidArgument
133 );
134
135 require_keys_neq!(*program_id, crate::ID, CoreError::InvalidArgument);
137 Ok(())
138 }
139
140 #[allow(clippy::too_many_arguments)]
141 pub(crate) fn invoke_general_callback<'info>(
142 &self,
143 kind: On,
144 authority: &Account<'info, CallbackAuthority>,
145 program: &AccountInfo<'info>,
146 shared_data: &AccountInfo<'info>,
147 partitioned_data: &AccountInfo<'info>,
148 owner: &AccountInfo<'info>,
149 action: &AccountInfo<'info>,
150 remaining_accounts: &[AccountInfo<'info>],
151 ) -> Result<()> {
152 use gmsol_callback::interface::{on_closed, on_created, on_executed, OnCallback};
153
154 let callback_version = self.callback_version;
155 self.validate_general_callback(program.key, shared_data.key, partitioned_data.key)?;
156
157 let ctx = CpiContext::new(
158 program.clone(),
159 OnCallback {
160 authority: authority.to_account_info(),
161 shared_data: shared_data.clone(),
162 partitioned_data: partitioned_data.clone(),
163 owner: owner.clone(),
164 action: action.clone(),
165 },
166 )
167 .with_remaining_accounts(remaining_accounts.to_vec());
168
169 let authority_bump = authority.bump();
170 let extra_account_count = remaining_accounts
171 .len()
172 .try_into()
173 .map_err(|_| error!(CoreError::Internal))?;
174
175 let signer_seeds = authority.signer_seeds();
176 match kind {
177 On::Created(kind) => on_created(
178 ctx.with_signer(&[&signer_seeds]),
179 authority_bump,
180 kind.into(),
181 callback_version,
182 extra_account_count,
183 ),
184 On::Updated(kind) => on_updated(
185 ctx.with_signer(&[&signer_seeds]),
186 authority_bump,
187 kind.into(),
188 callback_version,
189 extra_account_count,
190 ),
191 On::Executed(kind, success) => on_executed(
192 ctx.with_signer(&[&signer_seeds]),
193 authority_bump,
194 kind.into(),
195 callback_version,
196 success,
197 extra_account_count,
198 ),
199 On::Closed(kind) => on_closed(
200 ctx.with_signer(&[&signer_seeds]),
201 authority_bump,
202 kind.into(),
203 callback_version,
204 extra_account_count,
205 ),
206 }
207 }
208
209 pub(crate) fn completed(&mut self) -> Result<()> {
211 self.set_action_state(
212 self.action_state()?
213 .completed()
214 .map_err(CoreError::from)
215 .map_err(|err| error!(err))?,
216 );
217 Ok(())
218 }
219
220 pub(crate) fn cancelled(&mut self) -> Result<()> {
222 self.set_action_state(
223 self.action_state()?
224 .cancelled()
225 .map_err(CoreError::from)
226 .map_err(|err| error!(err))?,
227 );
228 Ok(())
229 }
230
231 pub(crate) fn signer(&self, seed: &'static [u8]) -> ActionSigner {
233 ActionSigner::new(seed, self.store, *self.creator(), self.nonce, self.bump)
234 }
235
236 pub fn owner(&self) -> &Pubkey {
238 &self.owner
239 }
240
241 pub fn receiver(&self) -> Pubkey {
243 *optional_address(&self.receiver).unwrap_or_else(|| self.owner())
244 }
245
246 pub fn id(&self) -> u64 {
248 self.id
249 }
250
251 pub fn store(&self) -> &Pubkey {
253 &self.store
254 }
255
256 pub fn market(&self) -> &Pubkey {
258 &self.market
259 }
260
261 pub fn nonce(&self) -> &[u8; 32] {
263 &self.nonce
264 }
265
266 pub fn max_execution_lamports(&self) -> u64 {
268 self.max_execution_lamports
269 }
270
271 pub fn updated_at(&self) -> i64 {
273 self.updated_at
274 }
275
276 pub fn updated_at_slot(&self) -> u64 {
278 self.updated_at_slot
279 }
280
281 pub fn bump(&self) -> u8 {
283 self.bump
284 }
285
286 pub fn creator(&self) -> &Pubkey {
289 &self.creator
290 }
291
292 pub fn rent_receiver(&self) -> &Pubkey {
294 &self.rent_receiver
295 }
296
297 #[inline(never)]
298 #[allow(clippy::too_many_arguments)]
299 pub(crate) fn init(
300 &mut self,
301 id: u64,
302 store: Pubkey,
303 market: Pubkey,
304 owner: Pubkey,
305 receiver: Pubkey,
306 nonce: [u8; 32],
307 bump: u8,
308 execution_lamports: u64,
309 should_unwrap_native_token: bool,
310 ) -> Result<()> {
311 let clock = Clock::get()?;
312 self.id = id;
313 self.store = store;
314 self.market = market;
315 self.owner = owner;
316
317 require!(
319 optional_address(&receiver).is_some(),
320 CoreError::InvalidArgument
321 );
322
323 self.receiver = receiver;
324 self.nonce = nonce;
325 self.max_execution_lamports = execution_lamports;
326 self.updated_at = clock.unix_timestamp;
327 self.updated_at_slot = clock.slot;
328 self.bump = bump;
329 self.creator = owner;
331 self.rent_receiver = owner;
333
334 self.set_should_unwrap_native_token(should_unwrap_native_token);
335
336 Ok(())
337 }
338
339 pub(crate) fn unchecked_set_creator(&mut self, creator: Pubkey) {
344 self.creator = creator;
345 }
346
347 pub(crate) fn set_rent_receiver(&mut self, rent_receiver: Pubkey) {
349 self.rent_receiver = rent_receiver;
350 }
351
352 pub(crate) fn updated(&mut self) -> Result<()> {
353 let clock = Clock::get()?;
354 self.updated_at = clock.unix_timestamp;
355 self.updated_at_slot = clock.slot;
356
357 Ok(())
358 }
359
360 pub fn should_unwrap_native_token(&self) -> bool {
362 self.flags.get_flag(ActionFlag::ShouldUnwrapNativeToken)
363 }
364
365 fn set_should_unwrap_native_token(&mut self, should_unwrap: bool) -> bool {
369 self.flags
370 .set_flag(ActionFlag::ShouldUnwrapNativeToken, should_unwrap)
371 }
372}
373
374pub struct ActionSigner {
376 seed: &'static [u8],
377 store: Pubkey,
378 owner: Pubkey,
379 nonce: NonceBytes,
380 bump_bytes: [u8; 1],
381}
382
383impl ActionSigner {
384 pub fn new(
386 seed: &'static [u8],
387 store: Pubkey,
388 owner: Pubkey,
389 nonce: NonceBytes,
390 bump: u8,
391 ) -> Self {
392 Self {
393 seed,
394 store,
395 owner,
396 nonce,
397 bump_bytes: [bump],
398 }
399 }
400
401 pub fn as_seeds(&self) -> [&[u8]; 5] {
403 [
404 self.seed,
405 self.store.as_ref(),
406 self.owner.as_ref(),
407 &self.nonce,
408 &self.bump_bytes,
409 ]
410 }
411}
412
413pub trait Action {
415 const MIN_EXECUTION_LAMPORTS: u64;
417
418 fn header(&self) -> &ActionHeader;
420}
421
422pub trait ActionExt: Action {
424 fn signer(&self) -> ActionSigner
426 where
427 Self: Seed,
428 {
429 self.header().signer(Self::SEED)
430 }
431
432 fn execution_lamports(&self, execution_lamports: u64) -> u64 {
434 execution_lamports.min(self.header().max_execution_lamports)
435 }
436
437 fn validate_balance(account: &AccountLoader<Self>, execution_lamports: u64) -> Result<()>
439 where
440 Self: ZeroCopy + Owner + InitSpace,
441 {
442 require_gte!(
443 execution_lamports,
444 Self::MIN_EXECUTION_LAMPORTS,
445 CoreError::NotEnoughExecutionFee
446 );
447 let balance = account.get_lamports().saturating_sub(execution_lamports);
448 let rent = Rent::get()?;
449 require!(
450 rent.is_exempt(balance, 8 + Self::INIT_SPACE),
451 CoreError::NotEnoughExecutionFee
452 );
453 Ok(())
454 }
455}
456
457impl<T: Action> ActionExt for T {}
458
459pub trait ActionParams {
461 fn execution_lamports(&self) -> u64;
463}
464
465pub trait Closable {
467 type ClosedEvent: Event + InitSpace;
469
470 fn to_closed_event(&self, address: &Pubkey, reason: &str) -> Result<Self::ClosedEvent>;
472}
473
474impl From<ActionError> for CoreError {
475 fn from(err: ActionError) -> Self {
476 msg!("Action error: {}", err);
477 match err {
478 ActionError::PreconditionsAreNotMet(_) => Self::PreconditionsAreNotMet,
479 }
480 }
481}
482
483pub(crate) enum On {
484 Created(ActionKind),
485 Updated(ActionKind),
486 Executed(ActionKind, bool),
487 Closed(ActionKind),
488}