gmsol_store/states/common/
action.rs

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/// Action Header.
18#[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.
24    action_state: u8,
25    /// The bump seed.
26    pub(crate) bump: u8,
27    flags: ActionFlagContainer,
28    callback_kind: u8,
29    callback_version: u8,
30    padding_0: [u8; 2],
31    /// Action id.
32    pub id: u64,
33    /// Store.
34    pub store: Pubkey,
35    /// Market.
36    pub market: Pubkey,
37    /// Owner.
38    pub owner: Pubkey,
39    /// Nonce bytes.
40    pub nonce: [u8; 32],
41    /// Max execution lamports.
42    pub(crate) max_execution_lamports: u64,
43    /// Last updated timestamp.
44    pub(crate) updated_at: i64,
45    /// Last updated slot.
46    pub(crate) updated_at_slot: u64,
47    /// Creator.
48    pub(crate) creator: Pubkey,
49    /// Rent receiver.
50    rent_receiver: Pubkey,
51    /// The output funds receiver.
52    receiver: Pubkey,
53    /// Callback program ID.
54    pub callback_program_id: Pubkey,
55    /// The account holding shared data for callback use.
56    pub callback_shared_data: Pubkey,
57    /// The account holding partitioned data for callback use.
58    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    /// Get action state.
73    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    /// Get callback kind.
82    pub fn callback_kind(&self) -> Result<ActionCallbackKind> {
83        ActionCallbackKind::try_from(self.callback_kind).map_err(|_| error!(CoreError::Internal))
84    }
85
86    /// Set general callback.
87    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    /// Validate the callback parameters.
108    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        // Recursion into this program is prohibited.
136        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    /// Transition to Completed state.
210    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    /// Transition to Cancelled state.
221    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    /// Get action signer.
232    pub(crate) fn signer(&self, seed: &'static [u8]) -> ActionSigner {
233        ActionSigner::new(seed, self.store, *self.creator(), self.nonce, self.bump)
234    }
235
236    /// Get the owner.
237    pub fn owner(&self) -> &Pubkey {
238        &self.owner
239    }
240
241    /// Get the receiver.
242    pub fn receiver(&self) -> Pubkey {
243        *optional_address(&self.receiver).unwrap_or_else(|| self.owner())
244    }
245
246    // Get the action id.
247    pub fn id(&self) -> u64 {
248        self.id
249    }
250
251    /// Get the store.
252    pub fn store(&self) -> &Pubkey {
253        &self.store
254    }
255
256    /// Get the market.
257    pub fn market(&self) -> &Pubkey {
258        &self.market
259    }
260
261    /// Get the nonce.
262    pub fn nonce(&self) -> &[u8; 32] {
263        &self.nonce
264    }
265
266    /// Get max execution lamports.
267    pub fn max_execution_lamports(&self) -> u64 {
268        self.max_execution_lamports
269    }
270
271    /// Get last updated timestamp.
272    pub fn updated_at(&self) -> i64 {
273        self.updated_at
274    }
275
276    /// Get last updated slot.
277    pub fn updated_at_slot(&self) -> u64 {
278        self.updated_at_slot
279    }
280
281    /// Get the bump.
282    pub fn bump(&self) -> u8 {
283        self.bump
284    }
285
286    /// Get the creator.
287    /// We assume that the action account's address is derived from that address.
288    pub fn creator(&self) -> &Pubkey {
289        &self.creator
290    }
291
292    /// Get the rent receiver.
293    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        // Receiver must not be the `None` address.
318        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        // The creator defaults to the `owner`.
330        self.creator = owner;
331        // The rent receiver defaults to the `owner`.
332        self.rent_receiver = owner;
333
334        self.set_should_unwrap_native_token(should_unwrap_native_token);
335
336        Ok(())
337    }
338
339    /// Set the creator.
340    ///
341    /// # CHECK
342    /// - The address of this action account must be derived from this address.
343    pub(crate) fn unchecked_set_creator(&mut self, creator: Pubkey) {
344        self.creator = creator;
345    }
346
347    /// Set the rent receiver.
348    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    /// Returns whether the native token should be unwrapped.
361    pub fn should_unwrap_native_token(&self) -> bool {
362        self.flags.get_flag(ActionFlag::ShouldUnwrapNativeToken)
363    }
364
365    /// Set whether the native token should be unwrapped.
366    ///
367    /// Returns the previous value.
368    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
374/// Action Signer.
375pub 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    /// Create a new action signer.
385    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    /// As signer seeds.
402    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
413/// Action.
414pub trait Action {
415    /// Min execution lamports.
416    const MIN_EXECUTION_LAMPORTS: u64;
417
418    /// Get the header.
419    fn header(&self) -> &ActionHeader;
420}
421
422/// Extension trait for [`Action`].
423pub trait ActionExt: Action {
424    /// Action signer.
425    fn signer(&self) -> ActionSigner
426    where
427        Self: Seed,
428    {
429        self.header().signer(Self::SEED)
430    }
431
432    /// Execution lamports.
433    fn execution_lamports(&self, execution_lamports: u64) -> u64 {
434        execution_lamports.min(self.header().max_execution_lamports)
435    }
436
437    /// Validate balance.
438    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
459/// Action Parameters.
460pub trait ActionParams {
461    /// Get max allowed execution fee in lamports.
462    fn execution_lamports(&self) -> u64;
463}
464
465/// Closable Action.
466pub trait Closable {
467    /// Closed Event.
468    type ClosedEvent: Event + InitSpace;
469
470    /// To closed event.
471    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}