light_token_interface/state/token/
top_up.rs

1//! Optimized top-up lamports calculation for Token accounts.
2
3use light_compressible::compression_info::CompressionInfo;
4use light_program_profiler::profile;
5#[cfg(target_os = "solana")]
6use pinocchio::account_info::AccountInfo;
7
8use super::ACCOUNT_TYPE_TOKEN_ACCOUNT;
9use crate::state::ExtensionType;
10
11/// Minimum size for Token with Compressible extension as first extension.
12/// 176 (offset to CompressionInfo) + 96 (CompressionInfo size) = 272
13pub const MIN_SIZE_WITH_COMPRESSIBLE: usize = COMPRESSION_INFO_OFFSET + COMPRESSION_INFO_SIZE;
14
15/// Offset to CompressionInfo when Compressible is first extension.
16/// 165 (base) + 1 (account_type) + 1 (Option) + 4 (Vec len) + 1 (ext disc) + 4 (ext header) = 176
17const COMPRESSION_INFO_OFFSET: usize = 176;
18
19/// Size of CompressionInfo struct.
20/// 2 (config_account_version) + 1 (compress_to_pubkey) + 1 (account_version) +
21/// 4 (lamports_per_write) + 32 (compression_authority) + 32 (rent_sponsor) +
22/// 8 (last_claimed_slot) + 4 (rent_exemption_paid) + 4 (_reserved) + 8 (rent_config) = 96
23const COMPRESSION_INFO_SIZE: usize = 96;
24
25/// Offset to account_type field.
26const ACCOUNT_TYPE_OFFSET: usize = 165;
27
28/// Offset to Option discriminator field.
29const OPTION_DISCRIMINATOR_OFFSET: usize = 166;
30
31/// Offset to first extension discriminator.
32const FIRST_EXT_DISCRIMINATOR_OFFSET: usize = 171;
33
34/// Option discriminator value for Some.
35const OPTION_SOME: u8 = 1;
36
37/// Calculate top-up lamports directly from Token account bytes.
38/// Returns None if account doesn't have Compressible extension as first extension.
39#[inline(always)]
40#[profile]
41pub fn top_up_lamports_from_slice(
42    data: &[u8],
43    current_lamports: u64,
44    current_slot: u64,
45) -> Option<u64> {
46    if data.len() < MIN_SIZE_WITH_COMPRESSIBLE
47        || data[ACCOUNT_TYPE_OFFSET] != ACCOUNT_TYPE_TOKEN_ACCOUNT
48        || data[OPTION_DISCRIMINATOR_OFFSET] != OPTION_SOME
49        || data[FIRST_EXT_DISCRIMINATOR_OFFSET] != ExtensionType::Compressible as u8
50    {
51        return None;
52    }
53
54    let info: &CompressionInfo = bytemuck::from_bytes(
55        &data[COMPRESSION_INFO_OFFSET..COMPRESSION_INFO_OFFSET + COMPRESSION_INFO_SIZE],
56    );
57
58    info.calculate_top_up_lamports(data.len() as u64, current_slot, current_lamports)
59        .ok()
60}
61
62/// Calculate top-up lamports from an AccountInfo.
63/// Returns None if account doesn't have Compressible extension as first extension.
64/// Note: Does not verify account owner. Fetches clock/rent sysvars internally if needed.
65/// Pass `current_slot` as 0 to fetch from Clock sysvar; non-zero values are used directly.
66#[cfg(target_os = "solana")]
67#[inline(always)]
68#[profile]
69pub fn top_up_lamports_from_account_info_unchecked(
70    account_info: &AccountInfo,
71    current_slot: &mut u64,
72) -> Option<u64> {
73    use pinocchio::sysvars::{clock::Clock, Sysvar};
74    let data = account_info.try_borrow_data().ok()?;
75    let current_lamports = account_info.lamports();
76    if *current_slot == 0 {
77        *current_slot = Clock::get().ok()?.slot;
78    }
79    top_up_lamports_from_slice(&data, current_lamports, *current_slot)
80}