pub mod account;
pub mod borsh_account;
pub(crate) mod cpi_const_wrapper;
mod impls; pub mod modifiers;
pub mod program;
pub mod rest;
pub mod single_set;
pub mod system_account;
pub mod sysvar;
pub mod validated_account;
pub use star_frame_proc::{AccountSet, ProgramAccount};
use crate::{prelude::*, ErrorCode};
use bytemuck::bytes_of;
use modifiers::{HasOwnerProgram, OwnerProgramDiscriminant};
use std::{mem::MaybeUninit, slice};
pub trait ProgramAccount: HasOwnerProgram {
const DISCRIMINANT: <Self::OwnerProgram as StarFrameProgram>::AccountDiscriminant;
#[must_use]
#[inline]
fn discriminant_bytes() -> Vec<u8> {
bytes_of(&Self::DISCRIMINANT).into()
}
#[allow(clippy::inline_always)]
#[inline(always)]
fn validate_account_info(info: AccountInfo) -> Result<()> {
validate_discriminant::<Self>(info)?;
if !info.owner().fast_eq(&Self::OwnerProgram::ID) {
bail!(
ProgramError::InvalidAccountOwner,
"Account {} owner {} does not match expected program ID {}",
info.pubkey(),
info.owner_pubkey(),
Self::OwnerProgram::ID
);
}
Ok(())
}
}
#[allow(clippy::inline_always)]
#[inline(always)]
fn validate_discriminant<T: ProgramAccount + ?Sized>(info: AccountInfo) -> Result<()> {
if size_of::<OwnerProgramDiscriminant<T>>() == 0 {
return Ok(());
}
if info.data_len() < size_of::<OwnerProgramDiscriminant<T>>() {
bail!(
ProgramError::AccountDataTooSmall,
"Account {} data length {} is less than expected discriminant size {}",
info.pubkey(),
info.data_len(),
size_of::<OwnerProgramDiscriminant<T>>()
);
}
info.can_borrow_data()?;
let data_ptr = info.data_ptr();
let matches = unsafe {
#[allow(clippy::cast_ptr_alignment)]
#[allow(clippy::cast_ptr_alignment)]
match size_of::<OwnerProgramDiscriminant<T>>() {
1 => *data_ptr == bytemuck::cast::<_, u8>(T::DISCRIMINANT),
2 => {
let data_val = data_ptr.cast::<u16>().read_unaligned();
let disc_val = bytemuck::cast::<_, u16>(T::DISCRIMINANT);
data_val == disc_val
}
4 => {
let data_val = data_ptr.cast::<u32>().read_unaligned();
let disc_val = bytemuck::cast::<_, u32>(T::DISCRIMINANT);
data_val == disc_val
}
8 => {
let data_val = data_ptr.cast::<u64>().read_unaligned();
let disc_val = bytemuck::cast::<_, u64>(T::DISCRIMINANT);
data_val == disc_val
}
_ => {
let data =
slice::from_raw_parts(data_ptr, size_of::<OwnerProgramDiscriminant<T>>());
data == bytemuck::bytes_of(&T::DISCRIMINANT)
}
}
};
if !matches {
bail!(
ErrorCode::DiscriminantMismatch,
"Account {} data does not match expected discriminant for program {}",
info.pubkey(),
T::OwnerProgram::ID
);
}
Ok(())
}
pub trait TryFromAccountsWithArgs<'a, D, V>:
AccountSetDecode<'a, D> + AccountSetValidate<V>
{
fn try_from_accounts_with_args(
accounts: &mut &'a [AccountInfo],
decode: D,
validate: V,
ctx: &mut Context,
) -> Result<Self> {
let mut set = Self::decode_accounts(accounts, decode, ctx)?;
set.validate_accounts(validate, ctx)?;
Ok(set)
}
fn try_from_account_with_args(
account: &'a AccountInfo,
decode: D,
validate: V,
ctx: &mut Context,
) -> Result<Self>
where
Self: SingleAccountSet,
{
let accounts = &mut slice::from_ref(account);
Self::try_from_accounts_with_args(accounts, decode, validate, ctx)
}
}
pub trait TryFromAccounts<'a>: TryFromAccountsWithArgs<'a, (), ()> {
fn try_from_accounts(accounts: &mut &'a [AccountInfo], ctx: &mut Context) -> Result<Self> {
Self::try_from_accounts_with_args(accounts, (), (), ctx)
}
fn try_from_account(account: &'a AccountInfo, ctx: &mut Context) -> Result<Self>
where
Self: SingleAccountSet,
{
Self::try_from_account_with_args(account, (), (), ctx)
}
}
impl<'a, T, D, V> TryFromAccountsWithArgs<'a, D, V> for T where
T: AccountSetDecode<'a, D> + AccountSetValidate<V>
{
}
impl<'a, T> TryFromAccounts<'a> for T where T: TryFromAccountsWithArgs<'a, (), ()> {}
pub trait AccountSetDecode<'a, A>: Sized {
fn decode_accounts(
accounts: &mut &'a [AccountInfo],
decode_input: A,
ctx: &mut Context,
) -> Result<Self>;
}
pub trait AccountSetValidate<A> {
#[rust_analyzer::completions(ignore_flyimport)]
fn validate_accounts(&mut self, validate_input: A, ctx: &mut Context) -> Result<()>;
}
pub trait AccountSetCleanup<A> {
#[rust_analyzer::completions(ignore_flyimport)]
fn cleanup_accounts(&mut self, cleanup_input: A, ctx: &mut Context) -> Result<()>;
}
pub type DynamicCpiAccountSetLen = typenum::U100;
pub unsafe trait CpiAccountSet {
type ContainsOption: typenum::Bit;
type CpiAccounts: Debug;
type AccountLen: typenum::Unsigned;
#[rust_analyzer::completions(ignore_flyimport)]
fn to_cpi_accounts(&self) -> Self::CpiAccounts;
fn write_account_infos<'a>(
program: Option<&'a AccountInfo>,
accounts: &'a Self::CpiAccounts,
index: &mut usize,
infos: &mut [MaybeUninit<&'a AccountInfo>],
) -> Result<()>;
fn write_account_metas<'a>(
program_id: &'a Pubkey,
accounts: &'a Self::CpiAccounts,
index: &mut usize,
metas: &mut [MaybeUninit<pinocchio::instruction::AccountMeta<'a>>],
);
}
#[rust_analyzer::completions(ignore_methods)]
pub trait ClientAccountSet {
type ClientAccounts: Clone + Debug;
const MIN_LEN: usize;
fn extend_account_metas(
program_id: &Pubkey,
accounts: &Self::ClientAccounts,
metas: &mut Vec<AccountMeta>,
);
}
pub trait CheckKey {
fn check_key(&self, key: &Pubkey) -> Result<()>;
}
static_assertions::assert_obj_safe!(CanAddLamports, CanFundRent);
#[rust_analyzer::completions(ignore_methods)]
pub trait CanAddLamports: Debug {
#[rust_analyzer::completions(ignore_flyimport)]
fn account_to_modify(&self) -> AccountInfo;
#[inline]
fn add_lamports(&self, lamports: u64) -> Result<()> {
*self.account_to_modify().try_borrow_mut_lamports()? += lamports;
Ok(())
}
}
pub trait CanFundRent: CanAddLamports {
#[rust_analyzer::completions(ignore_flyimport)]
fn can_create_account(&self) -> bool;
fn fund_rent(
&self,
recipient: &dyn SingleAccountSet,
lamports: u64,
ctx: &Context,
) -> Result<()>;
#[rust_analyzer::completions(ignore_flyimport)]
#[inline]
fn signer_seeds(&self) -> Option<Vec<&[u8]>> {
None
}
}
pub trait CanCloseAccount {
fn close_account(&self, recipient: &(impl CanAddLamports + ?Sized)) -> Result<()>
where
Self: HasOwnerProgram,
Self: Sized;
fn close_account_full(&self, recipient: &dyn CanAddLamports) -> Result<()>;
}
pub trait CanModifyRent {
fn normalize_rent(&self, funder: &(impl CanFundRent + ?Sized), ctx: &Context) -> Result<()>;
fn refund_rent(&self, recipient: &(impl CanAddLamports + ?Sized), ctx: &Context) -> Result<()>;
fn receive_rent(&self, funder: &(impl CanFundRent + ?Sized), ctx: &Context) -> Result<()>;
#[rust_analyzer::completions(ignore_flyimport)]
#[cfg_attr(not(feature = "cleanup_rent_warning"), allow(unused_variables))]
fn check_cleanup(&self, ctx: &Context) -> Result<()>;
}
pub trait CanSystemCreateAccount {
#[rust_analyzer::completions(ignore_flyimport)]
fn system_create_account(
&self,
funder: &(impl CanFundRent + ?Sized),
owner: Pubkey,
space: usize,
account_seeds: Option<&[&[u8]]>,
ctx: &Context,
) -> Result<()>;
}
#[doc(hidden)]
pub(crate) mod internal_reverse {
use super::*;
#[allow(clippy::inline_always)]
#[inline(always)]
pub fn _account_set_validate_reverse<T, A>(
validate_input: A,
this: &mut T,
ctx: &mut Context,
) -> Result<()>
where
T: AccountSetValidate<A>,
{
this.validate_accounts(validate_input, ctx)
}
#[allow(clippy::inline_always)]
#[inline(always)]
pub fn _account_set_cleanup_reverse<T, A>(
cleanup_input: A,
this: &mut T,
ctx: &mut Context,
) -> Result<()>
where
T: AccountSetCleanup<A>,
{
this.cleanup_accounts(cleanup_input, ctx)
}
}
pub(crate) mod prelude {
use super::*;
pub use super::{
AccountSet, CanCloseAccount as _, CanModifyRent as _, CheckKey as _, ProgramAccount,
TryFromAccounts, TryFromAccountsWithArgs,
};
pub use account::{
discriminant, Account, CloseAccount, NormalizeRent, ReceiveRent, RefundRent,
};
pub use borsh_account::BorshAccount;
pub use modifiers::{
init::{Create, CreateIfNeeded, Init},
mutable::Mut,
seeded::{GetSeeds, Seed, Seeded, Seeds, SeedsWithBump},
signer::Signer,
};
pub use program::Program;
pub use rest::Rest;
pub use single_set::SingleAccountSet;
pub use system_account::SystemAccount;
pub use sysvar::Sysvar;
pub use validated_account::{AccountValidate, ValidatedAccount};
}
#[cfg(test)]
mod test {
use crate::{account_set::AccountSetValidate, prelude::Context};
use star_frame_proc::AccountSet;
#[derive(AccountSet)]
#[validate(arg = &mut Vec<usize>, extra_validation = { arg.push(N); Ok(()) })]
struct InnerAccount<const N: usize>;
#[derive(AccountSet)]
#[validate(arg = &mut Vec<usize>)]
struct AccountSet123 {
#[validate(arg = &mut *arg)]
a: InnerAccount<1>,
#[validate(arg = &mut *arg)]
b: InnerAccount<2>,
#[validate(arg = &mut *arg)]
c: InnerAccount<3>,
}
#[derive(AccountSet)]
#[validate(arg = &mut Vec<usize>)]
struct AccountSet213 {
#[validate(arg = &mut *arg, requires = [b])]
a: InnerAccount<1>,
#[validate(arg = &mut *arg)]
b: InnerAccount<2>,
#[validate(arg = &mut *arg)]
c: InnerAccount<3>,
}
#[derive(AccountSet)]
#[validate(arg = &mut Vec<usize>)]
struct AccountSet312 {
#[validate(arg = &mut *arg, requires = [c])]
a: InnerAccount<1>,
#[validate(arg = &mut *arg, requires = [c])]
b: InnerAccount<2>,
#[validate(arg = &mut *arg)]
c: InnerAccount<3>,
}
#[derive(AccountSet)]
#[validate(arg = &mut Vec<usize>)]
struct AccountSet231 {
#[validate(arg = &mut *arg, requires = [c])]
a: InnerAccount<1>,
#[validate(arg = &mut *arg)]
b: InnerAccount<2>,
#[validate(arg = &mut *arg)]
c: InnerAccount<3>,
}
#[test]
fn test_validate() {
let mut vec = Vec::new();
let mut ctx = Context::default();
let mut set = AccountSet123 {
a: InnerAccount::<1>,
b: InnerAccount::<2>,
c: InnerAccount::<3>,
};
set.validate_accounts(&mut vec, &mut ctx).unwrap();
assert_eq!(vec, vec![1, 2, 3]);
vec.clear();
let mut set = AccountSet213 {
a: InnerAccount::<1>,
b: InnerAccount::<2>,
c: InnerAccount::<3>,
};
set.validate_accounts(&mut vec, &mut ctx).unwrap();
assert_eq!(vec, vec![2, 1, 3]);
vec.clear();
let mut set = AccountSet312 {
a: InnerAccount::<1>,
b: InnerAccount::<2>,
c: InnerAccount::<3>,
};
set.validate_accounts(&mut vec, &mut ctx).unwrap();
assert_eq!(vec, vec![3, 1, 2]);
vec.clear();
let mut set = AccountSet231 {
a: InnerAccount::<1>,
b: InnerAccount::<2>,
c: InnerAccount::<3>,
};
set.validate_accounts(&mut vec, &mut ctx).unwrap();
assert_eq!(vec, vec![2, 3, 1]);
}
}