hopper_core/account/
lifecycle.rs1use hopper_runtime::{error::ProgramError, AccountView, ProgramResult};
4
5pub const CLOSE_SENTINEL: u8 = 0xFF;
8
9#[inline(always)]
14pub fn zero_init(data: &mut [u8]) {
15 for byte in data.iter_mut() {
18 *byte = 0;
19 }
20}
21
22#[inline]
28pub fn safe_close(account: &AccountView, destination: &AccountView) -> ProgramResult {
29 let lamports = account.lamports();
30 if lamports == 0 {
31 return Ok(());
32 }
33
34 let new_dest = destination
36 .lamports()
37 .checked_add(lamports)
38 .ok_or(ProgramError::ArithmeticOverflow)?;
39 destination.set_lamports(new_dest);
40
41 account.set_lamports(0);
43
44 let mut data = account.try_borrow_mut()?;
46 zero_init(&mut data);
47
48 Ok(())
49}
50
51#[inline]
53pub fn safe_close_with_sentinel(account: &AccountView, destination: &AccountView) -> ProgramResult {
54 safe_close(account, destination)?;
55
56 let mut data = account.try_borrow_mut()?;
58 if !data.is_empty() {
59 data[0] = CLOSE_SENTINEL;
60 }
61
62 Ok(())
63}
64
65#[inline]
72pub fn safe_realloc(account: &AccountView, new_size: usize, payer: &AccountView) -> ProgramResult {
73 let rent_needed = rent_exempt_min_internal(new_size)?;
74 let current_lamports = account.lamports();
75 let deficit = rent_needed.saturating_sub(current_lamports);
76
77 let payer_lamports_after = if deficit > 0 {
78 Some(
79 payer
80 .lamports()
81 .checked_sub(deficit)
82 .ok_or(ProgramError::InsufficientFunds)?,
83 )
84 } else {
85 None
86 };
87 let account_lamports_after = if deficit > 0 {
88 Some(
89 current_lamports
90 .checked_add(deficit)
91 .ok_or(ProgramError::ArithmeticOverflow)?,
92 )
93 } else {
94 None
95 };
96
97 account.resize(new_size)?;
98
99 if let (Some(payer_lamports), Some(account_lamports)) =
100 (payer_lamports_after, account_lamports_after)
101 {
102 payer.set_lamports(payer_lamports);
103 account.set_lamports(account_lamports);
104 }
105
106 Ok(())
107}
108
109pub(crate) fn rent_exempt_min_internal(data_len: usize) -> Result<u64, ProgramError> {
111 let data_len = u64::try_from(data_len).map_err(|_| ProgramError::ArithmeticOverflow)?;
114 data_len
115 .checked_add(128)
116 .and_then(|bytes| bytes.checked_mul(6960))
117 .ok_or(ProgramError::ArithmeticOverflow)
118}
119
120#[cfg(all(test, feature = "hopper-native-backend"))]
121mod tests {
122 use super::*;
123 use hopper_native::{
124 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
125 };
126
127 fn make_account(data_len: usize, lamports: u64, seed: u8) -> (std::vec::Vec<u8>, AccountView) {
128 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + data_len];
129 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
130 unsafe {
133 raw.write(RuntimeAccount {
134 borrow_state: NOT_BORROWED,
135 is_signer: 1,
136 is_writable: 1,
137 executable: 0,
138 resize_delta: 0,
139 address: NativeAddress::new_from_array([seed; 32]),
140 owner: NativeAddress::new_from_array([2; 32]),
141 lamports,
142 data_len: data_len as u64,
143 });
144 }
145 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
147 let view = unsafe { core::mem::transmute::<NativeAccountView, AccountView>(backend) };
150 (backing, view)
151 }
152
153 #[test]
154 fn safe_realloc_checks_funding_before_resize() {
155 let (_account_backing, account) = make_account(16, 0, 1);
156 let (_payer_backing, payer) = make_account(0, 1, 2);
157
158 let result = safe_realloc(&account, 64, &payer);
159
160 assert_eq!(result, Err(ProgramError::InsufficientFunds));
161 assert_eq!(account.data_len(), 16);
162 assert_eq!(account.lamports(), 0);
163 assert_eq!(payer.lamports(), 1);
164 }
165
166 #[test]
167 fn safe_realloc_moves_lamports_after_successful_resize() {
168 let needed = rent_exempt_min_internal(32).unwrap();
169 let (_account_backing, account) = make_account(16, 0, 3);
170 let (_payer_backing, payer) = make_account(0, needed, 4);
171
172 safe_realloc(&account, 32, &payer).unwrap();
173
174 assert_eq!(account.data_len(), 32);
175 assert_eq!(account.lamports(), needed);
176 assert_eq!(payer.lamports(), 0);
177 }
178}