use crate::{
account_set::{
modifiers::{HasOwnerProgram, OwnerProgramDiscriminant, SignedAccount, WritableAccount},
CanAddLamports, CanCloseAccount, CanFundRent, CanModifyRent, CanSystemCreateAccount,
CheckKey,
},
prelude::*,
program::system,
ErrorCode,
};
use pinocchio::account_info::{Ref, RefMut};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy)]
pub struct SingleSetMeta {
pub signer: bool,
pub writable: bool,
}
impl SingleSetMeta {
#[must_use]
pub const fn default() -> Self {
Self {
signer: false,
writable: false,
}
}
}
pub trait SingleAccountSet {
fn meta() -> SingleSetMeta
where
Self: Sized;
fn account_info(&self) -> &AccountInfo;
#[inline]
fn account_meta(&self) -> AccountMeta {
let info = self.account_info();
AccountMeta {
pubkey: *info.pubkey(),
is_signer: info.is_signer(),
is_writable: info.is_writable(),
}
}
#[inline]
fn is_signer(&self) -> bool {
self.account_info().is_signer()
}
#[inline]
fn check_signer(&self) -> Result<()> {
if self.is_signer() {
Ok(())
} else {
bail!(
ErrorCode::ExpectedSigner,
"Account {} is not signed",
self.pubkey()
)
}
}
#[inline]
fn is_writable(&self) -> bool {
self.account_info().is_writable()
}
#[inline]
fn check_writable(&self) -> Result<()> {
if self.is_writable() {
Ok(())
} else {
bail!(
ErrorCode::ExpectedWritable,
"Account {} is not writable",
self.pubkey()
)
}
}
#[inline]
fn pubkey(&self) -> &Pubkey {
self.account_info().pubkey()
}
#[inline]
fn owner_pubkey(&self) -> Pubkey {
bytemuck::cast(*self.account_info().owner())
}
#[inline]
fn account_data(&self) -> Result<Ref<'_, [u8]>> {
self.account_info()
.try_borrow_data()
.with_ctx(|| format!("Failed to borrow data for account {}", self.pubkey()))
}
#[inline]
fn account_data_mut(&self) -> Result<RefMut<'_, [u8]>> {
self.account_info().try_borrow_mut_data().with_ctx(|| {
format!(
"Failed to borrow mutable data for account {}",
self.pubkey()
)
})
}
}
impl<T> CheckKey for T
where
T: SingleAccountSet,
{
#[inline]
fn check_key(&self, expected: &Pubkey) -> Result<()> {
if self.account_info().key().fast_eq(expected) {
Ok(())
} else {
bail!(
ErrorCode::AddressMismatch,
"Account key {} does not match expected public key {}",
self.pubkey(),
expected
)
}
}
}
impl<T> CanAddLamports for T
where
T: WritableAccount + Debug + ?Sized,
{
#[inline]
fn account_to_modify(&self) -> AccountInfo {
*self.account_info()
}
}
impl<T> CanFundRent for T
where
T: CanAddLamports + SignedAccount + ?Sized,
{
#[inline]
fn can_create_account(&self) -> bool {
true
}
#[inline]
fn fund_rent(
&self,
recipient: &dyn SingleAccountSet,
lamports: u64,
_ctx: &Context,
) -> Result<()> {
let cpi = System::cpi(
system::Transfer { lamports },
system::TransferCpiAccounts {
funder: *self.account_info(),
recipient: *recipient.account_info(),
},
None,
);
match self.signer_seeds() {
None => cpi.invoke()?,
Some(seeds) => cpi.invoke_signed(&[&seeds])?,
}
Ok(())
}
#[inline]
fn signer_seeds(&self) -> Option<Vec<&[u8]>> {
SignedAccount::signer_seeds(self)
}
}
impl<T> CanCloseAccount for T
where
T: SingleAccountSet + ?Sized,
{
#[inline]
fn close_account(&self, recipient: &(impl CanAddLamports + ?Sized)) -> Result<()>
where
Self: HasOwnerProgram,
Self: Sized,
{
let info = self.account_info();
info.resize(size_of::<OwnerProgramDiscriminant<Self>>())?;
info.account_data_mut()?.fill(u8::MAX);
recipient.add_lamports(info.lamports())?;
*info.try_borrow_mut_lamports()? = 0;
Ok(())
}
#[inline]
fn close_account_full(&self, recipient: &dyn CanAddLamports) -> Result<()> {
let info = self.account_info();
recipient.add_lamports(info.lamports())?;
info.close()?;
Ok(())
}
}
impl<T> CanModifyRent for T
where
T: SingleAccountSet + ?Sized,
{
#[inline]
fn normalize_rent(&self, funder: &(impl CanFundRent + ?Sized), ctx: &Context) -> Result<()> {
let account = self.account_info();
let rent = ctx.get_rent()?;
let lamports = *account.try_borrow_lamports()?;
let data_len = account.data_len();
let rent_lamports = rent.minimum_balance(data_len);
match rent_lamports.cmp(&lamports) {
Ordering::Equal => Ok(()),
Ordering::Greater => {
if lamports == 0 {
return Ok(());
}
let transfer_amount = rent_lamports - lamports;
CanFundRent::fund_rent(funder, &account, transfer_amount, ctx)?;
Ok(())
}
Ordering::Less => {
let transfer_amount = lamports - rent_lamports;
*account.try_borrow_mut_lamports()? -= transfer_amount;
funder.add_lamports(transfer_amount)?;
Ok(())
}
}
}
#[inline]
fn refund_rent(&self, recipient: &(impl CanAddLamports + ?Sized), ctx: &Context) -> Result<()> {
let account = self.account_info();
let rent = ctx.get_rent()?;
let lamports = *account.try_borrow_lamports()?;
let data_len = account.data_len();
let rent_lamports = rent.minimum_balance(data_len);
match rent_lamports.cmp(&lamports) {
Ordering::Equal => Ok(()),
Ordering::Greater => {
ensure!(
lamports > 0,
ProgramError::InsufficientFunds,
"Tried to refund rent from {} but does not have enough lamports to cover rent",
account.pubkey()
);
Ok(())
}
Ordering::Less => {
let transfer_amount = lamports - rent_lamports;
*account.try_borrow_mut_lamports()? -= transfer_amount;
recipient.add_lamports(transfer_amount)?;
Ok(())
}
}
}
#[inline]
fn receive_rent(&self, funder: &(impl CanFundRent + ?Sized), ctx: &Context) -> Result<()> {
let account = self.account_info();
let rent = ctx.get_rent()?;
let lamports = *account.try_borrow_lamports()?;
let data_len = account.data_len();
let rent_lamports = rent.minimum_balance(data_len);
if rent_lamports > lamports {
if lamports == 0 {
return Ok(());
}
let transfer_amount = rent_lamports - lamports;
CanFundRent::fund_rent(funder, &account, transfer_amount, ctx)?;
}
Ok(())
}
#[cfg_attr(not(feature = "cleanup_rent_warning"), allow(unused_variables))]
#[inline]
fn check_cleanup(&self, ctx: &Context) -> Result<()> {
#[cfg(feature = "cleanup_rent_warning")]
{
use std::cmp::Ordering;
let account = self.account_info();
if account.is_writable() {
let rent = ctx.get_rent()?;
let lamports = account.lamports();
let data_len = account.data_len();
let rent_lamports = rent.minimum_balance(data_len);
if rent_lamports.cmp(&lamports) == Ordering::Less {
pinocchio::msg!(
"{} was left with more lamports than required by rent",
account.pubkey()
);
}
}
}
Ok(())
}
}
impl<T> CanSystemCreateAccount for T
where
T: SingleAccountSet + ?Sized,
{
#[inline]
fn system_create_account(
&self,
funder: &(impl CanFundRent + ?Sized),
owner: Pubkey,
space: usize,
account_seeds: Option<&[&[u8]]>,
ctx: &Context,
) -> Result<()> {
let account = *self.account_info();
let current_lamports = account.lamports();
let exempt_lamports = ctx.get_rent()?.minimum_balance(space);
if current_lamports == 0 && funder.can_create_account() {
let funder_seeds: Option<Vec<&[u8]>> = funder.signer_seeds();
let seeds: &[&[&[u8]]] = match (&funder_seeds, account_seeds) {
(Some(signer_seeds), Some(account_seeds)) => &[signer_seeds, account_seeds],
(Some(signer_seeds), None) => &[signer_seeds],
(None, Some(account_seeds)) => &[account_seeds],
(None, None) => &[],
};
System::cpi(
system::CreateAccount {
lamports: exempt_lamports,
space: space as u64,
owner,
},
system::CreateAccountCpiAccounts {
funder: funder.account_to_modify(),
new_account: account,
},
None,
)
.invoke_signed(seeds)
.ctx("System::CreateAccount CPI failed")?;
} else {
let required_lamports = exempt_lamports.saturating_sub(current_lamports).max(1);
if required_lamports > 0 {
CanFundRent::fund_rent(funder, &account, required_lamports, ctx)
.ctx("Failed to fund rent")?;
}
let account_seeds: &[&[&[u8]]] = match &account_seeds {
Some(seeds) => &[seeds],
None => &[],
};
System::cpi(
system::Allocate {
space: space as u64,
},
system::AllocateCpiAccounts { account },
None,
)
.invoke_signed(account_seeds)
.ctx("System::Allocate CPI failed")?;
System::cpi(
system::Assign { owner },
system::AssignCpiAccounts { account },
None,
)
.invoke_signed(account_seeds)
.ctx("System::Assign CPI failed")?;
}
Ok(())
}
}