hopper_core/account/
realloc_guard.rs1use hopper_runtime::error::ProgramError;
26
27pub struct ReallocGuard<const N: usize> {
32 original: [u32; N],
34 current: [u32; N],
36 count: usize,
38 budget: u32,
40 consumed: u32,
42}
43
44impl<const N: usize> ReallocGuard<N> {
45 #[inline(always)]
47 pub const fn new(budget: u32) -> Self {
48 Self {
49 original: [0u32; N],
50 current: [0u32; N],
51 count: 0,
52 budget,
53 consumed: 0,
54 }
55 }
56
57 #[inline(always)]
63 pub fn register(&mut self, slot: usize, size: usize) -> Result<(), ProgramError> {
64 if slot >= N {
65 return Err(ProgramError::InvalidArgument);
66 }
67 let size32 = size as u32;
68 self.original[slot] = size32;
69 self.current[slot] = size32;
70 if slot >= self.count {
71 self.count = slot + 1;
72 }
73 Ok(())
74 }
75
76 #[inline]
81 pub fn check_growth(&self, slot: usize, new_size: usize) -> Result<(), ProgramError> {
82 if slot >= self.count {
83 return Err(ProgramError::InvalidArgument);
84 }
85 let new_size32 = new_size as u32;
86 let current = self.current[slot];
87
88 if new_size32 <= current {
89 return Ok(());
91 }
92
93 let delta = new_size32 - current;
94 let new_consumed = self
95 .consumed
96 .checked_add(delta)
97 .ok_or(ProgramError::ArithmeticOverflow)?;
98
99 if new_consumed > self.budget {
100 return Err(ProgramError::InvalidRealloc);
101 }
102
103 Ok(())
104 }
105
106 #[inline(always)]
111 pub fn commit_growth(&mut self, slot: usize, new_size: usize) -> Result<(), ProgramError> {
112 if slot >= self.count {
113 return Err(ProgramError::InvalidArgument);
114 }
115 let new_size32 = new_size as u32;
116 let current = self.current[slot];
117
118 if new_size32 > current {
119 let delta = new_size32 - current;
120 self.consumed += delta;
121 } else if new_size32 < current {
122 let credit = current - new_size32;
124 self.consumed = self.consumed.saturating_sub(credit);
125 }
126
127 self.current[slot] = new_size32;
128 Ok(())
129 }
130
131 #[inline(always)]
133 pub const fn remaining(&self) -> u32 {
134 self.budget.saturating_sub(self.consumed)
135 }
136
137 #[inline(always)]
139 pub const fn consumed(&self) -> u32 {
140 self.consumed
141 }
142
143 #[inline(always)]
145 pub const fn budget(&self) -> u32 {
146 self.budget
147 }
148
149 #[inline(always)]
151 pub fn slot_growth(&self, slot: usize) -> i32 {
152 if slot >= self.count {
153 return 0;
154 }
155 self.current[slot] as i32 - self.original[slot] as i32
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn basic_growth_tracking() {
165 let mut guard = ReallocGuard::<4>::new(1024);
166 guard.register(0, 100).unwrap();
167 guard.register(1, 200).unwrap();
168
169 assert!(guard.check_growth(0, 200).is_ok());
171 guard.commit_growth(0, 200).unwrap();
172 assert_eq!(guard.consumed(), 100);
173 assert_eq!(guard.remaining(), 924);
174
175 assert_eq!(guard.slot_growth(0), 100);
177 assert_eq!(guard.slot_growth(1), 0);
178 }
179
180 #[test]
181 fn budget_exceeded() {
182 let mut guard = ReallocGuard::<4>::new(100);
183 guard.register(0, 50).unwrap();
184
185 assert!(guard.check_growth(0, 200).is_err()); }
188
189 #[test]
190 fn shrink_returns_credit() {
191 let mut guard = ReallocGuard::<4>::new(200);
192 guard.register(0, 100).unwrap();
193
194 guard.commit_growth(0, 200).unwrap();
196 assert_eq!(guard.consumed(), 100);
197
198 guard.commit_growth(0, 150).unwrap();
200 assert_eq!(guard.consumed(), 50);
201 assert_eq!(guard.remaining(), 150);
202 }
203
204 #[test]
205 fn same_size_is_noop() {
206 let mut guard = ReallocGuard::<4>::new(100);
207 guard.register(0, 100).unwrap();
208
209 assert!(guard.check_growth(0, 100).is_ok());
210 guard.commit_growth(0, 100).unwrap();
211 assert_eq!(guard.consumed(), 0);
212 }
213
214 #[test]
215 fn register_out_of_bounds() {
216 let mut guard = ReallocGuard::<2>::new(1024);
217 assert!(guard.register(0, 100).is_ok());
218 assert!(guard.register(1, 200).is_ok());
219 assert!(guard.register(2, 300).is_err()); }
221
222 #[test]
223 fn commit_unregistered_slot() {
224 let mut guard = ReallocGuard::<4>::new(1024);
225 guard.register(0, 100).unwrap();
226 assert!(guard.commit_growth(3, 200).is_err());
228 }
229}