1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! Rent-exemption helpers.
//!
//! Solana's rent model charges accounts for storage on a per-byte-year
//! basis. An account that holds at least
//! `(data_len + ACCOUNT_STORAGE_OVERHEAD) * LAMPORTS_PER_BYTE_YEAR *
//! EXEMPTION_THRESHOLD` lamports is *rent-exempt* and never loses
//! balance to rent collection.
//!
//! This module exposes two things:
//!
//! 1. [`minimum_balance`] — a pure function computing the rent-exempt
//! threshold for a given `data_len`, using the cluster constants
//! that have been in effect on Solana mainnet since launch
//! (`lamports_per_byte_year = 3480`, `exemption_threshold = 2 years`,
//! `account_storage_overhead = 128 bytes`). These values are
//! governed on-chain but have never been changed. If the cluster
//! ever re-governs them, the check will be conservative
//! (strictly requiring at least the pre-governance threshold) —
//! still safe, just not tight.
//!
//! 2. [`check_rent_exempt`] — the runtime guard backing the
//! `#[account(rent_exempt = enforce)]` field keyword emitted by
//! `#[hopper::context]`. Compares `account.lamports()` to
//! `minimum_balance(account.data_len())` and returns
//! `ProgramError::AccountNotRentExempt` (Solana's canonical error
//! code, routed through `ProgramError::Custom`) on failure.
//!
//! ## Why not use `sol_get_rent_sysvar`?
//!
//! The syscall is ~100 CU and returns the same values this module
//! hard-codes. Using the constants inline lets the check run at
//! zero additional CU beyond the comparison. Programs that need to
//! read the live Rent sysvar for other reasons (rent-collection
//! scheduling, etc.) can still invoke the syscall directly; this
//! helper is specifically for the rent-exemption gate where the
//! constants suffice.
use crateAccountView;
use crateProgramError;
use crateProgramResult;
/// Lamports charged per byte of account storage per year.
///
/// Fixed at 3480 since Solana mainnet launch and unchanged through
/// 2026. The value is governed on-chain via the Rent sysvar but no
/// cluster vote has ever modified it.
pub const LAMPORTS_PER_BYTE_YEAR: u64 = 3_480;
/// Years of rent an account must prepay to be exempt.
///
/// Fixed at 2.0 since launch. Represented as an integer here because
/// the multiplication always lands on an integer result for the
/// given `LAMPORTS_PER_BYTE_YEAR`.
pub const EXEMPTION_THRESHOLD_YEARS: u64 = 2;
/// Fixed per-account storage overhead the cluster charges on top of
/// user data. 128 bytes (header + metadata).
pub const ACCOUNT_STORAGE_OVERHEAD: u64 = 128;
/// Minimum lamport balance for an account with `data_len` bytes of
/// data to be rent-exempt under the current Solana cluster constants.
///
/// `(data_len + 128) * 3480 * 2` — constant-folded at the call site
/// when `data_len` is a `const`.
pub const
/// Assert that `account` holds enough lamports to be rent-exempt for
/// its current data length. Used by the `#[account(rent_exempt =
/// enforce)]` constraint lowering in `hopper-macros-proc`.
///
/// Returns `ProgramError::AccountNotRentExempt` on underrun. The error
/// code maps to Solana's canonical `InstructionError::RentEpoch`
/// (built-in 29) when surfaced through the runtime.