gmsol_store/states/common/
action.rs1use 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#[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: u8,
21 pub(crate) bump: u8,
23 flags: ActionFlagContainer,
24 padding_0: [u8; 4],
25 pub id: u64,
27 pub store: Pubkey,
29 pub market: Pubkey,
31 pub owner: Pubkey,
33 pub nonce: [u8; 32],
35 pub(crate) max_execution_lamports: u64,
37 pub(crate) updated_at: i64,
39 pub(crate) updated_at_slot: u64,
41 pub(crate) creator: Pubkey,
43 rent_receiver: Pubkey,
45 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#[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,
81 Completed,
83 Cancelled,
85}
86
87impl ActionState {
88 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 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 pub fn is_completed_or_cancelled(&self) -> bool {
106 matches!(self, Self::Completed | Self::Cancelled)
107 }
108
109 pub fn is_pending(&self) -> bool {
111 matches!(self, Self::Pending)
112 }
113
114 pub fn is_cancelled(&self) -> bool {
116 matches!(self, Self::Cancelled)
117 }
118
119 pub fn is_completed(&self) -> bool {
121 matches!(self, Self::Completed)
122 }
123}
124
125#[repr(u8)]
127#[non_exhaustive]
128#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
129pub enum ActionFlag {
130 ShouldUnwrapNativeToken,
132 }
134
135gmsol_utils::flags!(ActionFlag, MAX_FLAGS, u8);
136
137impl ActionHeader {
138 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 pub(crate) fn completed(&mut self) -> Result<()> {
149 self.set_action_state(self.action_state()?.completed()?);
150 Ok(())
151 }
152
153 pub(crate) fn cancelled(&mut self) -> Result<()> {
155 self.set_action_state(self.action_state()?.cancelled()?);
156 Ok(())
157 }
158
159 pub(crate) fn signer(&self, seed: &'static [u8]) -> ActionSigner {
161 ActionSigner::new(seed, self.store, *self.creator(), self.nonce, self.bump)
162 }
163
164 pub fn owner(&self) -> &Pubkey {
166 &self.owner
167 }
168
169 pub fn receiver(&self) -> Pubkey {
171 *optional_address(&self.receiver).unwrap_or_else(|| self.owner())
172 }
173
174 pub fn id(&self) -> u64 {
176 self.id
177 }
178
179 pub fn store(&self) -> &Pubkey {
181 &self.store
182 }
183
184 pub fn market(&self) -> &Pubkey {
186 &self.market
187 }
188
189 pub fn nonce(&self) -> &[u8; 32] {
191 &self.nonce
192 }
193
194 pub fn max_execution_lamports(&self) -> u64 {
196 self.max_execution_lamports
197 }
198
199 pub fn updated_at(&self) -> i64 {
201 self.updated_at
202 }
203
204 pub fn updated_at_slot(&self) -> u64 {
206 self.updated_at_slot
207 }
208
209 pub fn bump(&self) -> u8 {
211 self.bump
212 }
213
214 pub fn creator(&self) -> &Pubkey {
217 &self.creator
218 }
219
220 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 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 self.creator = owner;
259 self.rent_receiver = owner;
261
262 self.set_should_unwrap_native_token(should_unwrap_native_token);
263
264 Ok(())
265 }
266
267 pub(crate) fn unchecked_set_creator(&mut self, creator: Pubkey) {
272 self.creator = creator;
273 }
274
275 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 pub fn should_unwrap_native_token(&self) -> bool {
290 self.flags.get_flag(ActionFlag::ShouldUnwrapNativeToken)
291 }
292
293 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
302pub 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 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 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
341pub trait Action {
343 const MIN_EXECUTION_LAMPORTS: u64;
345
346 fn header(&self) -> &ActionHeader;
348}
349
350pub trait ActionExt: Action {
352 fn signer(&self) -> ActionSigner
354 where
355 Self: Seed,
356 {
357 self.header().signer(Self::SEED)
358 }
359
360 fn execution_lamports(&self, execution_lamports: u64) -> u64 {
362 execution_lamports.min(self.header().max_execution_lamports)
363 }
364
365 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
387pub trait ActionParams {
389 fn execution_lamports(&self) -> u64;
391}
392
393pub trait Closable {
395 type ClosedEvent: Event + InitSpace;
397
398 fn to_closed_event(&self, address: &Pubkey, reason: &str) -> Result<Self::ClosedEvent>;
400}