gmsol_store/states/common/
action.rs

1use anchor_lang::{prelude::*, ZeroCopy};
2use gmsol_utils::InitSpace;
3
4use crate::{
5    events::Event,
6    states::{NonceBytes, Seed},
7    utils::pubkey::optional_address,
8    CoreError,
9};
10
11const MAX_FLAGS: usize = 8;
12
13/// Action Header.
14#[zero_copy]
15#[cfg_attr(feature = "debug", derive(Debug))]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct ActionHeader {
18    version: u8,
19    /// Action State.
20    action_state: u8,
21    /// The bump seed.
22    pub(crate) bump: u8,
23    flags: ActionFlagContainer,
24    padding_0: [u8; 4],
25    /// Action id.
26    pub id: u64,
27    /// Store.
28    pub store: Pubkey,
29    /// Market.
30    pub market: Pubkey,
31    /// Owner.
32    pub owner: Pubkey,
33    /// Nonce bytes.
34    pub nonce: [u8; 32],
35    /// Max execution lamports.
36    pub(crate) max_execution_lamports: u64,
37    /// Last updated timestamp.
38    pub(crate) updated_at: i64,
39    /// Last updated slot.
40    pub(crate) updated_at_slot: u64,
41    /// Creator.
42    pub(crate) creator: Pubkey,
43    /// Rent receiver.
44    rent_receiver: Pubkey,
45    /// The output funds receiver.
46    receiver: Pubkey,
47    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
48    reserved: [u8; 256],
49}
50
51impl Default for ActionHeader {
52    fn default() -> Self {
53        bytemuck::Zeroable::zeroed()
54    }
55}
56
57/// Action State.
58#[non_exhaustive]
59#[repr(u8)]
60#[derive(
61    Clone,
62    Copy,
63    num_enum::IntoPrimitive,
64    num_enum::TryFromPrimitive,
65    PartialEq,
66    Eq,
67    strum::EnumString,
68    strum::Display,
69    AnchorSerialize,
70    AnchorDeserialize,
71    InitSpace,
72)]
73#[strum(serialize_all = "snake_case")]
74#[num_enum(error_type(name = CoreError, constructor = CoreError::unknown_action_state))]
75#[cfg_attr(feature = "debug", derive(Debug))]
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
78pub enum ActionState {
79    /// Pending.
80    Pending,
81    /// Completed.
82    Completed,
83    /// Cancelled.
84    Cancelled,
85}
86
87impl ActionState {
88    /// Transition to Completed State.
89    pub fn completed(self) -> Result<Self> {
90        let Self::Pending = self else {
91            return err!(CoreError::PreconditionsAreNotMet);
92        };
93        Ok(Self::Completed)
94    }
95
96    /// Transition to Cancelled State.
97    pub fn cancelled(self) -> Result<Self> {
98        let Self::Pending = self else {
99            return err!(CoreError::PreconditionsAreNotMet);
100        };
101        Ok(Self::Cancelled)
102    }
103
104    /// Check if the state is completed or cancelled.
105    pub fn is_completed_or_cancelled(&self) -> bool {
106        matches!(self, Self::Completed | Self::Cancelled)
107    }
108
109    /// Check if the state is pending.
110    pub fn is_pending(&self) -> bool {
111        matches!(self, Self::Pending)
112    }
113
114    /// Check if the state is cancelled.
115    pub fn is_cancelled(&self) -> bool {
116        matches!(self, Self::Cancelled)
117    }
118
119    /// Check if the state is completed.
120    pub fn is_completed(&self) -> bool {
121        matches!(self, Self::Completed)
122    }
123}
124
125/// Action Flags.
126#[repr(u8)]
127#[non_exhaustive]
128#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
129pub enum ActionFlag {
130    /// Should unwrap native token.
131    ShouldUnwrapNativeToken,
132    // CHECK: should have no more than `MAX_FLAGS` of flags.
133}
134
135gmsol_utils::flags!(ActionFlag, MAX_FLAGS, u8);
136
137impl ActionHeader {
138    /// Get action state.
139    pub fn action_state(&self) -> Result<ActionState> {
140        ActionState::try_from(self.action_state).map_err(|err| error!(err))
141    }
142
143    fn set_action_state(&mut self, new_state: ActionState) {
144        self.action_state = new_state.into();
145    }
146
147    /// Transition to Completed state.
148    pub(crate) fn completed(&mut self) -> Result<()> {
149        self.set_action_state(self.action_state()?.completed()?);
150        Ok(())
151    }
152
153    /// Transition to Cancelled state.
154    pub(crate) fn cancelled(&mut self) -> Result<()> {
155        self.set_action_state(self.action_state()?.cancelled()?);
156        Ok(())
157    }
158
159    /// Get action signer.
160    pub(crate) fn signer(&self, seed: &'static [u8]) -> ActionSigner {
161        ActionSigner::new(seed, self.store, *self.creator(), self.nonce, self.bump)
162    }
163
164    /// Get the owner.
165    pub fn owner(&self) -> &Pubkey {
166        &self.owner
167    }
168
169    /// Get the receiver.
170    pub fn receiver(&self) -> Pubkey {
171        *optional_address(&self.receiver).unwrap_or_else(|| self.owner())
172    }
173
174    // Get the action id.
175    pub fn id(&self) -> u64 {
176        self.id
177    }
178
179    /// Get the store.
180    pub fn store(&self) -> &Pubkey {
181        &self.store
182    }
183
184    /// Get the market.
185    pub fn market(&self) -> &Pubkey {
186        &self.market
187    }
188
189    /// Get the nonce.
190    pub fn nonce(&self) -> &[u8; 32] {
191        &self.nonce
192    }
193
194    /// Get max execution lamports.
195    pub fn max_execution_lamports(&self) -> u64 {
196        self.max_execution_lamports
197    }
198
199    /// Get last updated timestamp.
200    pub fn updated_at(&self) -> i64 {
201        self.updated_at
202    }
203
204    /// Get last updated slot.
205    pub fn updated_at_slot(&self) -> u64 {
206        self.updated_at_slot
207    }
208
209    /// Get the bump.
210    pub fn bump(&self) -> u8 {
211        self.bump
212    }
213
214    /// Get the creator.
215    /// We assume that the action account's address is derived from that address.
216    pub fn creator(&self) -> &Pubkey {
217        &self.creator
218    }
219
220    /// Get the rent receiver.
221    pub fn rent_receiver(&self) -> &Pubkey {
222        &self.rent_receiver
223    }
224
225    #[inline(never)]
226    #[allow(clippy::too_many_arguments)]
227    pub(crate) fn init(
228        &mut self,
229        id: u64,
230        store: Pubkey,
231        market: Pubkey,
232        owner: Pubkey,
233        receiver: Pubkey,
234        nonce: [u8; 32],
235        bump: u8,
236        execution_lamports: u64,
237        should_unwrap_native_token: bool,
238    ) -> Result<()> {
239        let clock = Clock::get()?;
240        self.id = id;
241        self.store = store;
242        self.market = market;
243        self.owner = owner;
244
245        // Receiver must not be the `None` address.
246        require!(
247            optional_address(&receiver).is_some(),
248            CoreError::InvalidArgument
249        );
250
251        self.receiver = receiver;
252        self.nonce = nonce;
253        self.max_execution_lamports = execution_lamports;
254        self.updated_at = clock.unix_timestamp;
255        self.updated_at_slot = clock.slot;
256        self.bump = bump;
257        // The creator defaults to the `owner`.
258        self.creator = owner;
259        // The rent receiver defaults to the `owner`.
260        self.rent_receiver = owner;
261
262        self.set_should_unwrap_native_token(should_unwrap_native_token);
263
264        Ok(())
265    }
266
267    /// Set the creator.
268    ///
269    /// # CHECK
270    /// - The address of this action account must be derived from this address.
271    pub(crate) fn unchecked_set_creator(&mut self, creator: Pubkey) {
272        self.creator = creator;
273    }
274
275    /// Set the rent receiver.
276    pub(crate) fn set_rent_receiver(&mut self, rent_receiver: Pubkey) {
277        self.rent_receiver = rent_receiver;
278    }
279
280    pub(crate) fn updated(&mut self) -> Result<()> {
281        let clock = Clock::get()?;
282        self.updated_at = clock.unix_timestamp;
283        self.updated_at_slot = clock.slot;
284
285        Ok(())
286    }
287
288    /// Returns whether the native token should be unwrapped.
289    pub fn should_unwrap_native_token(&self) -> bool {
290        self.flags.get_flag(ActionFlag::ShouldUnwrapNativeToken)
291    }
292
293    /// Set whether the native token should be unwrapped.
294    ///
295    /// Returns the previous vaule.
296    fn set_should_unwrap_native_token(&mut self, should_unwrap: bool) -> bool {
297        self.flags
298            .set_flag(ActionFlag::ShouldUnwrapNativeToken, should_unwrap)
299    }
300}
301
302/// Action Signer.
303pub struct ActionSigner {
304    seed: &'static [u8],
305    store: Pubkey,
306    owner: Pubkey,
307    nonce: NonceBytes,
308    bump_bytes: [u8; 1],
309}
310
311impl ActionSigner {
312    /// Create a new action signer.
313    pub fn new(
314        seed: &'static [u8],
315        store: Pubkey,
316        owner: Pubkey,
317        nonce: NonceBytes,
318        bump: u8,
319    ) -> Self {
320        Self {
321            seed,
322            store,
323            owner,
324            nonce,
325            bump_bytes: [bump],
326        }
327    }
328
329    /// As signer seeds.
330    pub fn as_seeds(&self) -> [&[u8]; 5] {
331        [
332            self.seed,
333            self.store.as_ref(),
334            self.owner.as_ref(),
335            &self.nonce,
336            &self.bump_bytes,
337        ]
338    }
339}
340
341/// Action.
342pub trait Action {
343    /// Min execution lamports.
344    const MIN_EXECUTION_LAMPORTS: u64;
345
346    /// Get the header.
347    fn header(&self) -> &ActionHeader;
348}
349
350/// Extentsion trait for [`Action`].
351pub trait ActionExt: Action {
352    /// Action signer.
353    fn signer(&self) -> ActionSigner
354    where
355        Self: Seed,
356    {
357        self.header().signer(Self::SEED)
358    }
359
360    /// Execution lamports.
361    fn execution_lamports(&self, execution_lamports: u64) -> u64 {
362        execution_lamports.min(self.header().max_execution_lamports)
363    }
364
365    /// Validate balance.
366    fn validate_balance(account: &AccountLoader<Self>, execution_lamports: u64) -> Result<()>
367    where
368        Self: ZeroCopy + Owner + InitSpace,
369    {
370        require_gte!(
371            execution_lamports,
372            Self::MIN_EXECUTION_LAMPORTS,
373            CoreError::NotEnoughExecutionFee
374        );
375        let balance = account.get_lamports().saturating_sub(execution_lamports);
376        let rent = Rent::get()?;
377        require!(
378            rent.is_exempt(balance, 8 + Self::INIT_SPACE),
379            CoreError::NotEnoughExecutionFee
380        );
381        Ok(())
382    }
383}
384
385impl<T: Action> ActionExt for T {}
386
387/// Action Parameters.
388pub trait ActionParams {
389    /// Get max allowed execution fee in lamports.
390    fn execution_lamports(&self) -> u64;
391}
392
393/// Closable Action.
394pub trait Closable {
395    /// Closed Event.
396    type ClosedEvent: Event + InitSpace;
397
398    /// To closed event.
399    fn to_closed_event(&self, address: &Pubkey, reason: &str) -> Result<Self::ClosedEvent>;
400}