use anchor_lang::{prelude::*, ZeroCopy};
use gmsol_callback::{cpi::on_updated, interface::ActionKind};
use gmsol_utils::{
action::{ActionCallbackKind, ActionError, MAX_ACTION_FLAGS},
InitSpace,
};
use crate::{
events::Event,
states::{callback::CallbackAuthority, NonceBytes, Seed},
utils::pubkey::optional_address,
CoreError,
};
pub use gmsol_utils::action::{ActionFlag, ActionState};
#[zero_copy]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ActionHeader {
version: u8,
action_state: u8,
pub(crate) bump: u8,
flags: ActionFlagContainer,
callback_kind: u8,
callback_version: u8,
padding_0: [u8; 2],
pub id: u64,
pub store: Pubkey,
pub market: Pubkey,
pub owner: Pubkey,
pub nonce: [u8; 32],
pub(crate) max_execution_lamports: u64,
pub(crate) updated_at: i64,
pub(crate) updated_at_slot: u64,
pub(crate) creator: Pubkey,
rent_receiver: Pubkey,
receiver: Pubkey,
pub callback_program_id: Pubkey,
pub callback_shared_data: Pubkey,
pub callback_partitioned_data: Pubkey,
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
reserved: [u8; 160],
}
impl Default for ActionHeader {
fn default() -> Self {
bytemuck::Zeroable::zeroed()
}
}
gmsol_utils::flags!(ActionFlag, MAX_ACTION_FLAGS, u8);
impl ActionHeader {
pub fn action_state(&self) -> Result<ActionState> {
ActionState::try_from(self.action_state).map_err(|_| error!(CoreError::UnknownActionState))
}
fn set_action_state(&mut self, new_state: ActionState) {
self.action_state = new_state.into();
}
pub fn callback_kind(&self) -> Result<ActionCallbackKind> {
ActionCallbackKind::try_from(self.callback_kind).map_err(|_| error!(CoreError::Internal))
}
pub(crate) fn set_general_callback(
&mut self,
program_id: &Pubkey,
callback_version: u8,
shared_data: &Pubkey,
partitioned_data: &Pubkey,
) -> Result<()> {
require_eq!(
self.callback_kind()?,
ActionCallbackKind::Disabled,
CoreError::PreconditionsAreNotMet
);
self.callback_version = callback_version;
self.callback_kind = ActionCallbackKind::General.into();
self.callback_program_id = *program_id;
self.callback_shared_data = *shared_data;
self.callback_partitioned_data = *partitioned_data;
Ok(())
}
pub(crate) fn validate_general_callback(
&self,
program_id: &Pubkey,
shared_data: &Pubkey,
partitioned_data: &Pubkey,
) -> Result<()> {
require_eq!(
self.callback_kind()?,
ActionCallbackKind::General,
CoreError::InvalidArgument
);
require_keys_eq!(
*program_id,
self.callback_program_id,
CoreError::InvalidArgument
);
require_keys_eq!(
*shared_data,
self.callback_shared_data,
CoreError::InvalidArgument
);
require_keys_eq!(
*partitioned_data,
self.callback_partitioned_data,
CoreError::InvalidArgument
);
require_keys_neq!(*program_id, crate::ID, CoreError::InvalidArgument);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn invoke_general_callback<'info>(
&self,
kind: On,
authority: &Account<'info, CallbackAuthority>,
program: &AccountInfo<'info>,
shared_data: &AccountInfo<'info>,
partitioned_data: &AccountInfo<'info>,
owner: &AccountInfo<'info>,
action: &AccountInfo<'info>,
remaining_accounts: &[AccountInfo<'info>],
) -> Result<()> {
use gmsol_callback::interface::{on_closed, on_created, on_executed, OnCallback};
let callback_version = self.callback_version;
self.validate_general_callback(program.key, shared_data.key, partitioned_data.key)?;
let ctx = CpiContext::new(
program.clone(),
OnCallback {
authority: authority.to_account_info(),
shared_data: shared_data.clone(),
partitioned_data: partitioned_data.clone(),
owner: owner.clone(),
action: action.clone(),
},
)
.with_remaining_accounts(remaining_accounts.to_vec());
let authority_bump = authority.bump();
let extra_account_count = remaining_accounts
.len()
.try_into()
.map_err(|_| error!(CoreError::Internal))?;
let signer_seeds = authority.signer_seeds();
match kind {
On::Created(kind) => on_created(
ctx.with_signer(&[&signer_seeds]),
authority_bump,
kind.into(),
callback_version,
extra_account_count,
),
On::Updated(kind) => on_updated(
ctx.with_signer(&[&signer_seeds]),
authority_bump,
kind.into(),
callback_version,
extra_account_count,
),
On::Executed(kind, success) => on_executed(
ctx.with_signer(&[&signer_seeds]),
authority_bump,
kind.into(),
callback_version,
success,
extra_account_count,
),
On::Closed(kind) => on_closed(
ctx.with_signer(&[&signer_seeds]),
authority_bump,
kind.into(),
callback_version,
extra_account_count,
),
}
}
pub(crate) fn completed(&mut self) -> Result<()> {
self.set_action_state(
self.action_state()?
.completed()
.map_err(CoreError::from)
.map_err(|err| error!(err))?,
);
Ok(())
}
pub(crate) fn cancelled(&mut self) -> Result<()> {
self.set_action_state(
self.action_state()?
.cancelled()
.map_err(CoreError::from)
.map_err(|err| error!(err))?,
);
Ok(())
}
pub(crate) fn signer(&self, seed: &'static [u8]) -> ActionSigner {
ActionSigner::new(seed, self.store, *self.creator(), self.nonce, self.bump)
}
pub fn owner(&self) -> &Pubkey {
&self.owner
}
pub fn receiver(&self) -> Pubkey {
*optional_address(&self.receiver).unwrap_or_else(|| self.owner())
}
pub fn id(&self) -> u64 {
self.id
}
pub fn store(&self) -> &Pubkey {
&self.store
}
pub fn market(&self) -> &Pubkey {
&self.market
}
pub fn nonce(&self) -> &[u8; 32] {
&self.nonce
}
pub fn max_execution_lamports(&self) -> u64 {
self.max_execution_lamports
}
pub fn updated_at(&self) -> i64 {
self.updated_at
}
pub fn updated_at_slot(&self) -> u64 {
self.updated_at_slot
}
pub fn bump(&self) -> u8 {
self.bump
}
pub fn creator(&self) -> &Pubkey {
&self.creator
}
pub fn rent_receiver(&self) -> &Pubkey {
&self.rent_receiver
}
#[inline(never)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn init(
&mut self,
id: u64,
store: Pubkey,
market: Pubkey,
owner: Pubkey,
receiver: Pubkey,
nonce: [u8; 32],
bump: u8,
execution_lamports: u64,
should_unwrap_native_token: bool,
) -> Result<()> {
let clock = Clock::get()?;
self.id = id;
self.store = store;
self.market = market;
self.owner = owner;
require!(
optional_address(&receiver).is_some(),
CoreError::InvalidArgument
);
self.receiver = receiver;
self.nonce = nonce;
self.max_execution_lamports = execution_lamports;
self.updated_at = clock.unix_timestamp;
self.updated_at_slot = clock.slot;
self.bump = bump;
self.creator = owner;
self.rent_receiver = owner;
self.set_should_unwrap_native_token(should_unwrap_native_token);
Ok(())
}
pub(crate) fn unchecked_set_creator(&mut self, creator: Pubkey) {
self.creator = creator;
}
pub(crate) fn set_rent_receiver(&mut self, rent_receiver: Pubkey) {
self.rent_receiver = rent_receiver;
}
pub(crate) fn updated(&mut self) -> Result<()> {
let clock = Clock::get()?;
self.updated_at = clock.unix_timestamp;
self.updated_at_slot = clock.slot;
Ok(())
}
pub fn should_unwrap_native_token(&self) -> bool {
self.flags.get_flag(ActionFlag::ShouldUnwrapNativeToken)
}
fn set_should_unwrap_native_token(&mut self, should_unwrap: bool) -> bool {
self.flags
.set_flag(ActionFlag::ShouldUnwrapNativeToken, should_unwrap)
}
}
pub struct ActionSigner {
seed: &'static [u8],
store: Pubkey,
owner: Pubkey,
nonce: NonceBytes,
bump_bytes: [u8; 1],
}
impl ActionSigner {
pub fn new(
seed: &'static [u8],
store: Pubkey,
owner: Pubkey,
nonce: NonceBytes,
bump: u8,
) -> Self {
Self {
seed,
store,
owner,
nonce,
bump_bytes: [bump],
}
}
pub fn as_seeds(&self) -> [&[u8]; 5] {
[
self.seed,
self.store.as_ref(),
self.owner.as_ref(),
&self.nonce,
&self.bump_bytes,
]
}
}
pub trait Action {
const MIN_EXECUTION_LAMPORTS: u64;
fn header(&self) -> &ActionHeader;
}
pub trait ActionExt: Action {
fn signer(&self) -> ActionSigner
where
Self: Seed,
{
self.header().signer(Self::SEED)
}
fn execution_lamports(&self, execution_lamports: u64) -> u64 {
execution_lamports.min(self.header().max_execution_lamports)
}
fn validate_balance(account: &AccountLoader<Self>, execution_lamports: u64) -> Result<()>
where
Self: ZeroCopy + Owner + InitSpace,
{
require_gte!(
execution_lamports,
Self::MIN_EXECUTION_LAMPORTS,
CoreError::NotEnoughExecutionFee
);
let balance = account.get_lamports().saturating_sub(execution_lamports);
let rent = Rent::get()?;
require!(
rent.is_exempt(balance, 8 + Self::INIT_SPACE),
CoreError::NotEnoughExecutionFee
);
Ok(())
}
}
impl<T: Action> ActionExt for T {}
pub trait ActionParams {
fn execution_lamports(&self) -> u64;
}
pub trait Closable {
type ClosedEvent: Event + InitSpace;
fn to_closed_event(&self, address: &Pubkey, reason: &str) -> Result<Self::ClosedEvent>;
}
impl From<ActionError> for CoreError {
fn from(err: ActionError) -> Self {
msg!("Action error: {}", err);
match err {
ActionError::PreconditionsAreNotMet(_) => Self::PreconditionsAreNotMet,
}
}
}
pub(crate) enum On {
Created(ActionKind),
Updated(ActionKind),
Executed(ActionKind, bool),
Closed(ActionKind),
}