use std::borrow::Cow;
use light_compressible::rent::RentConfig;
use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress;
use solana_account_info::AccountInfo;
use solana_clock::Clock;
use solana_pubkey::Pubkey;
use solana_sysvar::Sysvar;
use crate::{instruction::PackedAccounts, AnchorDeserialize, AnchorSerialize, ProgramError};
pub trait Pack {
type Packed: AnchorSerialize + Clone + std::fmt::Debug;
fn pack(&self, remaining_accounts: &mut PackedAccounts) -> Result<Self::Packed, ProgramError>;
}
pub trait Unpack {
type Unpacked;
fn unpack(
&self,
remaining_accounts: &[AccountInfo],
) -> Result<Self::Unpacked, crate::ProgramError>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
#[repr(u8)]
pub enum AccountState {
Initialized,
Frozen,
}
pub trait HasCompressionInfo {
fn compression_info(&self) -> Result<&CompressionInfo, ProgramError>;
fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, ProgramError>;
fn compression_info_mut_opt(&mut self) -> &mut Option<CompressionInfo>;
fn set_compression_info_none(&mut self) -> Result<(), ProgramError>;
}
pub trait CompressedInitSpace {
const COMPRESSED_INIT_SPACE: usize;
}
pub trait CompressAs {
type Output: crate::AnchorSerialize
+ crate::AnchorDeserialize
+ crate::LightDiscriminator
+ crate::account::Size
+ HasCompressionInfo
+ Default
+ Clone;
fn compress_as(&self) -> Cow<'_, Self::Output>;
}
#[derive(Debug, Clone, Default, PartialEq, AnchorSerialize, AnchorDeserialize)]
pub struct CompressionInfo {
pub config_version: u16,
pub lamports_per_write: u32,
pub last_claimed_slot: u64,
pub rent_config: RentConfig,
pub state: CompressionState,
}
#[derive(Debug, Clone, Default, AnchorSerialize, AnchorDeserialize, PartialEq)]
pub enum CompressionState {
#[default]
Uninitialized,
Decompressed,
Compressed,
}
impl CompressionInfo {
pub fn new_from_config(cfg: &crate::interface::LightConfig, current_slot: u64) -> Self {
Self {
config_version: cfg.version as u16,
lamports_per_write: cfg.write_top_up,
last_claimed_slot: current_slot,
rent_config: cfg.rent_config,
state: CompressionState::Decompressed,
}
}
pub fn new_decompressed() -> Result<Self, crate::ProgramError> {
Ok(Self {
config_version: 0,
lamports_per_write: 0,
last_claimed_slot: Clock::get()?.slot,
rent_config: RentConfig::default(),
state: CompressionState::Decompressed,
})
}
pub fn bump_last_claimed_slot(&mut self) -> Result<(), crate::ProgramError> {
self.last_claimed_slot = Clock::get()?.slot;
Ok(())
}
pub fn set_last_claimed_slot(&mut self, slot: u64) {
self.last_claimed_slot = slot;
}
pub fn last_claimed_slot(&self) -> u64 {
self.last_claimed_slot
}
pub fn set_compressed(&mut self) {
self.state = CompressionState::Compressed;
}
pub fn is_compressed(&self) -> bool {
self.state == CompressionState::Compressed
}
}
impl CompressionInfo {
pub fn calculate_top_up_lamports(
&self,
num_bytes: u64,
current_slot: u64,
current_lamports: u64,
rent_exemption_lamports: u64,
) -> u64 {
use light_compressible::rent::AccountRentState;
let state = AccountRentState {
num_bytes,
current_slot,
current_lamports,
last_claimed_slot: self.last_claimed_slot(),
};
if let Some(rent_deficit) =
state.is_compressible(&self.rent_config, rent_exemption_lamports)
{
return self.lamports_per_write as u64 + rent_deficit;
}
let epochs_funded_ahead =
state.epochs_funded_ahead(&self.rent_config, rent_exemption_lamports);
if epochs_funded_ahead >= self.rent_config.max_funded_epochs as u64 {
return 0;
}
self.lamports_per_write as u64
}
pub fn top_up_rent<'a>(
&self,
account_info: &AccountInfo<'a>,
payer_info: &AccountInfo<'a>,
system_program_info: &AccountInfo<'a>,
) -> Result<(), crate::ProgramError> {
use solana_clock::Clock;
use solana_sysvar::{rent::Rent, Sysvar};
let bytes = account_info.data_len() as u64;
let current_lamports = account_info.lamports();
let current_slot = Clock::get()?.slot;
let rent_exemption_lamports = Rent::get()?.minimum_balance(bytes as usize);
let top_up = self.calculate_top_up_lamports(
bytes,
current_slot,
current_lamports,
rent_exemption_lamports,
);
if top_up > 0 {
transfer_lamports_cpi(payer_info, account_info, system_program_info, top_up)?;
}
Ok(())
}
}
pub trait Space {
const INIT_SPACE: usize;
}
impl Space for CompressionInfo {
const INIT_SPACE: usize = 2 + 4 + 8 + core::mem::size_of::<RentConfig>() + 1;
}
#[cfg(feature = "anchor")]
impl anchor_lang::Space for CompressionInfo {
const INIT_SPACE: usize = <Self as Space>::INIT_SPACE;
}
pub const OPTION_COMPRESSION_INFO_SPACE: usize = 1 + CompressionInfo::INIT_SPACE;
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct CompressedAccountData<T> {
pub meta: CompressedAccountMetaNoLamportsNoAddress,
pub data: T,
}
pub fn claim_completed_epoch_rent<'info, A>(
account_info: &AccountInfo<'info>,
account_data: &mut A,
rent_sponsor: &AccountInfo<'info>,
) -> Result<Option<u64>, ProgramError>
where
A: HasCompressionInfo,
{
use light_compressible::rent::{AccountRentState, SLOTS_PER_EPOCH};
use solana_sysvar::rent::Rent;
let current_slot = Clock::get()?.slot;
let bytes = account_info.data_len() as u64;
let current_lamports = account_info.lamports();
let rent_exemption_lamports = Rent::get()
.map_err(|_| ProgramError::Custom(0))?
.minimum_balance(bytes as usize);
let ci = account_data.compression_info_mut()?;
let state = AccountRentState {
num_bytes: bytes,
current_slot,
current_lamports,
last_claimed_slot: ci.last_claimed_slot(),
};
if state
.is_compressible(&ci.rent_config, rent_exemption_lamports)
.is_some()
{
return Ok(None);
}
let claimable = state.calculate_claimable_rent(&ci.rent_config, rent_exemption_lamports);
if let Some(amount) = claimable {
if amount > 0 {
let completed_epochs = state.get_completed_epochs();
ci.set_last_claimed_slot(
ci.last_claimed_slot()
.saturating_add(completed_epochs * SLOTS_PER_EPOCH),
);
{
let mut src = account_info
.try_borrow_mut_lamports()
.map_err(|_| ProgramError::Custom(0))?;
let mut dst = rent_sponsor
.try_borrow_mut_lamports()
.map_err(|_| ProgramError::Custom(0))?;
let new_src = src
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
let new_dst = dst.checked_add(amount).ok_or(ProgramError::Custom(0))?;
**src = new_src;
**dst = new_dst;
}
return Ok(Some(amount));
}
}
Ok(Some(0))
}
fn transfer_lamports_cpi<'a>(
from: &AccountInfo<'a>,
to: &AccountInfo<'a>,
system_program: &AccountInfo<'a>,
lamports: u64,
) -> Result<(), ProgramError> {
use solana_cpi::invoke;
use solana_instruction::{AccountMeta, Instruction};
const SYSTEM_PROGRAM_ID: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
];
let mut instruction_data = vec![2, 0, 0, 0];
instruction_data.extend_from_slice(&lamports.to_le_bytes());
let transfer_instruction = Instruction {
program_id: Pubkey::from(SYSTEM_PROGRAM_ID),
accounts: vec![
AccountMeta::new(*from.key, true),
AccountMeta::new(*to.key, false),
],
data: instruction_data,
};
invoke(
&transfer_instruction,
&[from.clone(), to.clone(), system_program.clone()],
)
}