gmsol_store/states/common/
action.rs

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