1use std::{cell::RefCell, rc::Rc};
2
3use crate::{
4 constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE_IN_BYTES_U64, WORD_SIZE_IN_BYTES_USIZE},
5 errors::{ExceptionalHalt, InternalError, VMError},
6};
7use ExceptionalHalt::OutOfBounds;
8use bytes::Bytes;
9use ethrex_common::{
10 U256,
11 utils::{u256_from_big_endian_const, u256_to_big_endian},
12};
13
14#[derive(Debug, Clone)]
18pub struct Memory {
19 pub buffer: Rc<RefCell<Vec<u8>>>,
20 pub len: usize,
21 current_base: usize,
22}
23
24impl Memory {
25 #[inline]
26 pub fn new() -> Self {
27 Self {
28 buffer: Rc::new(RefCell::new(Vec::new())),
29 len: 0,
30 current_base: 0,
31 }
32 }
33
34 #[inline]
42 pub fn reset_for_reuse(&mut self) {
43 self.buffer.borrow_mut().clear();
44 self.len = 0;
45 self.current_base = 0;
46 }
47
48 #[inline]
50 pub fn next_memory(&self) -> Memory {
51 let mut mem = self.clone();
52 mem.current_base = mem.buffer.borrow().len();
53 mem.len = 0;
54 mem
55 }
56
57 #[inline]
61 pub fn clean_from_base(&self) {
62 #[expect(unsafe_code)]
63 unsafe {
64 self.buffer
65 .borrow_mut()
66 .get_unchecked_mut(self.current_base..(self.current_base.wrapping_add(self.len)))
67 .fill(0);
68 }
69 }
70
71 #[cfg(target_arch = "riscv64")]
75 #[inline]
76 pub fn truncate_to_base(&self) {
77 self.buffer.borrow_mut().truncate(self.current_base);
78 }
79
80 #[inline]
82 pub fn len(&self) -> usize {
83 self.len
84 }
85
86 #[inline]
87 pub fn is_empty(&self) -> bool {
88 self.len() == 0
89 }
90
91 pub fn live_bytes(&self) -> Vec<u8> {
94 if self.len == 0 {
95 return Vec::new();
96 }
97 let buf = self.buffer.borrow();
98 let end = self.current_base.saturating_add(self.len);
99 buf.get(self.current_base..end)
100 .map(<[u8]>::to_vec)
101 .unwrap_or_default()
102 }
103
104 #[inline(always)]
108 pub fn resize(&mut self, new_memory_size: usize) -> Result<(), VMError> {
109 if new_memory_size == 0 {
110 return Ok(());
111 }
112
113 let new_memory_size = new_memory_size
114 .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE)
115 .ok_or(OutOfBounds)?;
116
117 let current_len = self.len();
118
119 if new_memory_size <= current_len {
120 return Ok(());
121 }
122
123 self.len = new_memory_size;
124
125 let mut buffer = self.buffer.borrow_mut();
126
127 #[allow(clippy::arithmetic_side_effects)]
128 let real_new_memory_size = new_memory_size + self.current_base;
129
130 if real_new_memory_size > buffer.len() {
131 let new_size = real_new_memory_size.next_multiple_of(64);
133 buffer.resize(new_size, 0);
134 }
135
136 Ok(())
137 }
138
139 #[inline]
141 pub fn load_range(&mut self, offset: usize, size: usize) -> Result<Bytes, VMError> {
142 if size == 0 {
143 return Ok(Bytes::new());
144 }
145
146 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
147 self.resize(new_size)?;
148
149 let true_offset = offset.wrapping_add(self.current_base);
150
151 let buf = self.buffer.borrow();
152
153 #[allow(unsafe_code)]
155 unsafe {
156 Ok(Bytes::copy_from_slice(buf.get_unchecked(
157 true_offset..(true_offset.wrapping_add(size)),
158 )))
159 }
160 }
161
162 #[inline]
171 pub fn with_range<R>(
172 &mut self,
173 offset: usize,
174 size: usize,
175 f: impl FnOnce(&[u8]) -> R,
176 ) -> Result<R, VMError> {
177 if size == 0 {
178 return Ok(f(&[]));
179 }
180
181 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
182 self.resize(new_size)?;
183
184 let true_offset = offset.wrapping_add(self.current_base);
185
186 let buf = self.buffer.borrow();
187
188 #[allow(unsafe_code)]
190 let range = unsafe { buf.get_unchecked(true_offset..(true_offset.wrapping_add(size))) };
191 Ok(f(range))
192 }
193
194 #[inline(always)]
196 pub fn load_range_const<const N: usize>(&mut self, offset: usize) -> Result<[u8; N], VMError> {
197 let new_size = offset.checked_add(N).ok_or(OutOfBounds)?;
198 self.resize(new_size)?;
199
200 let true_offset = offset.checked_add(self.current_base).ok_or(OutOfBounds)?;
201
202 let buf = self.buffer.borrow();
203 #[allow(unsafe_code)]
205 unsafe {
206 Ok(*buf
207 .get_unchecked(true_offset..(true_offset.wrapping_add(N)))
208 .as_ptr()
209 .cast::<[u8; N]>())
210 }
211 }
212
213 #[inline(always)]
215 pub fn load_word(&mut self, offset: usize) -> Result<U256, VMError> {
216 let value: [u8; 32] = self.load_range_const(offset)?;
217 Ok(u256_from_big_endian_const(value))
218 }
219
220 #[inline(always)]
224 fn store(&self, data: &[u8], at_offset: usize, data_size: usize) -> Result<(), VMError> {
225 if data_size == 0 {
226 return Ok(());
227 }
228
229 let real_offset = self.current_base.wrapping_add(at_offset);
230
231 let mut buffer = self.buffer.borrow_mut();
232
233 let real_data_size = data_size.min(data.len());
234
235 #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
237 #[allow(unsafe_code)]
238 unsafe {
239 std::ptr::copy_nonoverlapping(
240 data.get_unchecked(..real_data_size).as_ptr(),
241 buffer
242 .get_unchecked_mut(real_offset..(real_offset + real_data_size))
243 .as_mut_ptr(),
244 real_data_size,
245 );
246 }
247
248 Ok(())
249 }
250
251 #[inline(always)]
253 pub fn store_data(&mut self, offset: usize, data: &[u8]) -> Result<(), VMError> {
254 if data.is_empty() {
255 return Ok(());
256 }
257 let new_size = offset.checked_add(data.len()).ok_or(OutOfBounds)?;
258 self.resize(new_size)?;
259 self.store(data, offset, data.len())
260 }
261
262 #[inline(always)]
264 pub fn store_data_zero_padded(
265 &mut self,
266 offset: usize,
267 data: &[u8],
268 total_size: usize,
269 ) -> Result<(), VMError> {
270 if total_size == 0 {
271 return Ok(());
272 }
273
274 let new_size = offset.checked_add(total_size).ok_or(OutOfBounds)?;
275 self.resize(new_size)?;
276
277 let copy_size = data.len().min(total_size);
278 if copy_size > 0 {
279 self.store(data, offset, copy_size)?;
280 }
281
282 #[allow(clippy::arithmetic_side_effects)]
283 if copy_size < total_size {
284 let zero_offset = offset.wrapping_add(copy_size);
287 let zero_size = total_size - copy_size;
288 let real_offset = self.current_base.wrapping_add(zero_offset);
289 let mut buffer = self.buffer.borrow_mut();
290
291 #[expect(unsafe_code)]
293 unsafe {
294 buffer
295 .get_unchecked_mut(real_offset..real_offset.wrapping_add(zero_size))
296 .fill(0);
297 }
298 }
299
300 Ok(())
301 }
302
303 #[inline(always)]
305 pub fn store_word(&mut self, offset: usize, word: U256) -> Result<(), VMError> {
306 let new_size: usize = offset
307 .checked_add(WORD_SIZE_IN_BYTES_USIZE)
308 .ok_or(OutOfBounds)?;
309
310 self.resize(new_size)?;
311 self.store(&u256_to_big_endian(word), offset, WORD_SIZE_IN_BYTES_USIZE)?;
312 Ok(())
313 }
314
315 pub fn copy_within(
319 &mut self,
320 from_offset: usize,
321 to_offset: usize,
322 size: usize,
323 ) -> Result<(), VMError> {
324 if size == 0 {
325 return Ok(());
326 }
327
328 self.resize(
329 to_offset
330 .max(from_offset)
331 .checked_add(size)
332 .ok_or(InternalError::Overflow)?,
333 )?;
334
335 let true_from_offset = from_offset
336 .checked_add(self.current_base)
337 .ok_or(OutOfBounds)?;
338
339 let true_to_offset = to_offset
340 .checked_add(self.current_base)
341 .ok_or(OutOfBounds)?;
342 let mut buffer = self.buffer.borrow_mut();
343
344 buffer.copy_within(
345 true_from_offset
346 ..(true_from_offset
347 .checked_add(size)
348 .ok_or(InternalError::Overflow)?),
349 true_to_offset,
350 );
351
352 Ok(())
353 }
354
355 #[inline(always)]
356 pub fn store_zeros(&mut self, offset: usize, size: usize) -> Result<(), VMError> {
357 if size == 0 {
358 return Ok(());
359 }
360
361 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
362 self.resize(new_size)?;
363
364 let real_offset = self.current_base.wrapping_add(offset);
365 let mut buffer = self.buffer.borrow_mut();
366
367 #[expect(unsafe_code)]
369 unsafe {
370 buffer
371 .get_unchecked_mut(real_offset..(real_offset.wrapping_add(size)))
372 .fill(0);
373 }
374
375 Ok(())
376 }
377}
378
379impl Default for Memory {
380 fn default() -> Self {
381 Self::new()
382 }
383}
384
385#[inline]
388pub fn expansion_cost(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
389 let cost = if new_memory_size <= current_memory_size {
390 0
391 } else {
392 cost(new_memory_size)?.wrapping_sub(cost(current_memory_size)?)
395 };
396 Ok(cost)
397}
398
399#[inline]
402fn cost(memory_size: usize) -> Result<u64, VMError> {
403 let memory_size = u64::try_from(memory_size).map_err(|_| InternalError::TypeConversion)?;
404
405 let words = memory_size.div_ceil(WORD_SIZE_IN_BYTES_U64);
407
408 #[expect(clippy::arithmetic_side_effects)]
411 let gas_cost = words * words / MEMORY_EXPANSION_QUOTIENT + 3 * words;
412
413 Ok(gas_cost)
414}
415
416#[inline]
417pub fn calculate_memory_size(offset: usize, size: usize) -> Result<usize, VMError> {
418 if size == 0 {
419 return Ok(0);
420 }
421
422 offset
423 .checked_add(size)
424 .and_then(|sum| sum.checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE))
425 .ok_or(OutOfBounds.into())
426}