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.buf_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.buf_mut().truncate(self.current_base);
78 }
79
80 #[expect(unsafe_code, reason = "single active frame; no overlapping borrows")]
87 #[inline]
88 fn buf(&self) -> &Vec<u8> {
89 unsafe { &*self.buffer.as_ptr() }
90 }
91
92 #[expect(unsafe_code, reason = "single active frame; no overlapping borrows")]
94 #[expect(clippy::mut_from_ref, reason = "single active frame; see buf()")]
95 #[inline]
96 fn buf_mut(&self) -> &mut Vec<u8> {
97 unsafe { &mut *self.buffer.as_ptr() }
98 }
99
100 #[inline]
102 pub fn len(&self) -> usize {
103 self.len
104 }
105
106 #[inline]
107 pub fn is_empty(&self) -> bool {
108 self.len() == 0
109 }
110
111 pub fn live_bytes(&self) -> Vec<u8> {
114 if self.len == 0 {
115 return Vec::new();
116 }
117 let buf = self.buf();
118 let end = self.current_base.saturating_add(self.len);
119 buf.get(self.current_base..end)
120 .map(<[u8]>::to_vec)
121 .unwrap_or_default()
122 }
123
124 #[inline(always)]
128 pub fn resize(&mut self, new_memory_size: usize) -> Result<(), VMError> {
129 if new_memory_size == 0 {
130 return Ok(());
131 }
132
133 let new_memory_size = new_memory_size
134 .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE)
135 .ok_or(OutOfBounds)?;
136
137 let current_len = self.len();
138
139 if new_memory_size <= current_len {
140 return Ok(());
141 }
142
143 self.len = new_memory_size;
144
145 let buffer = self.buf_mut();
146
147 #[allow(clippy::arithmetic_side_effects)]
148 let real_new_memory_size = new_memory_size + self.current_base;
149
150 if real_new_memory_size > buffer.len() {
151 let new_size = real_new_memory_size.next_multiple_of(64);
153 buffer.resize(new_size, 0);
154 }
155
156 Ok(())
157 }
158
159 #[inline]
161 pub fn load_range(&mut self, offset: usize, size: usize) -> Result<Bytes, VMError> {
162 if size == 0 {
163 return Ok(Bytes::new());
164 }
165
166 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
167 self.resize(new_size)?;
168
169 let true_offset = offset.wrapping_add(self.current_base);
170
171 let buf = self.buf();
172
173 #[allow(unsafe_code)]
175 unsafe {
176 Ok(Bytes::copy_from_slice(buf.get_unchecked(
177 true_offset..(true_offset.wrapping_add(size)),
178 )))
179 }
180 }
181
182 #[inline]
191 pub fn with_range<R>(
192 &mut self,
193 offset: usize,
194 size: usize,
195 f: impl FnOnce(&[u8]) -> R,
196 ) -> Result<R, VMError> {
197 if size == 0 {
198 return Ok(f(&[]));
199 }
200
201 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
202 self.resize(new_size)?;
203
204 let true_offset = offset.wrapping_add(self.current_base);
205
206 let buf = self.buf();
207
208 #[allow(unsafe_code)]
210 let range = unsafe { buf.get_unchecked(true_offset..(true_offset.wrapping_add(size))) };
211 Ok(f(range))
212 }
213
214 #[inline(always)]
216 pub fn load_range_const<const N: usize>(&mut self, offset: usize) -> Result<[u8; N], VMError> {
217 let new_size = offset.checked_add(N).ok_or(OutOfBounds)?;
218 self.resize(new_size)?;
219
220 let true_offset = offset.checked_add(self.current_base).ok_or(OutOfBounds)?;
221
222 let buf = self.buf();
223 #[allow(unsafe_code)]
225 unsafe {
226 Ok(*buf
227 .get_unchecked(true_offset..(true_offset.wrapping_add(N)))
228 .as_ptr()
229 .cast::<[u8; N]>())
230 }
231 }
232
233 #[inline(always)]
235 pub fn load_word(&mut self, offset: usize) -> Result<U256, VMError> {
236 let value: [u8; 32] = self.load_range_const(offset)?;
237 Ok(u256_from_big_endian_const(value))
238 }
239
240 #[inline(always)]
244 fn store(&self, data: &[u8], at_offset: usize, data_size: usize) -> Result<(), VMError> {
245 if data_size == 0 {
246 return Ok(());
247 }
248
249 let real_offset = self.current_base.wrapping_add(at_offset);
250
251 let buffer = self.buf_mut();
252
253 let real_data_size = data_size.min(data.len());
254
255 #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
257 #[allow(unsafe_code)]
258 unsafe {
259 std::ptr::copy_nonoverlapping(
260 data.get_unchecked(..real_data_size).as_ptr(),
261 buffer
262 .get_unchecked_mut(real_offset..(real_offset + real_data_size))
263 .as_mut_ptr(),
264 real_data_size,
265 );
266 }
267
268 Ok(())
269 }
270
271 #[inline(always)]
273 pub fn store_data(&mut self, offset: usize, data: &[u8]) -> Result<(), VMError> {
274 if data.is_empty() {
275 return Ok(());
276 }
277 let new_size = offset.checked_add(data.len()).ok_or(OutOfBounds)?;
278 self.resize(new_size)?;
279 self.store(data, offset, data.len())
280 }
281
282 #[inline(always)]
284 pub fn store_data_zero_padded(
285 &mut self,
286 offset: usize,
287 data: &[u8],
288 total_size: usize,
289 ) -> Result<(), VMError> {
290 if total_size == 0 {
291 return Ok(());
292 }
293
294 let new_size = offset.checked_add(total_size).ok_or(OutOfBounds)?;
295 self.resize(new_size)?;
296
297 let copy_size = data.len().min(total_size);
298 if copy_size > 0 {
299 self.store(data, offset, copy_size)?;
300 }
301
302 #[allow(clippy::arithmetic_side_effects)]
303 if copy_size < total_size {
304 let zero_offset = offset.wrapping_add(copy_size);
307 let zero_size = total_size - copy_size;
308 let real_offset = self.current_base.wrapping_add(zero_offset);
309 let buffer = self.buf_mut();
310
311 #[expect(unsafe_code)]
313 unsafe {
314 buffer
315 .get_unchecked_mut(real_offset..real_offset.wrapping_add(zero_size))
316 .fill(0);
317 }
318 }
319
320 Ok(())
321 }
322
323 #[inline(always)]
325 pub fn store_word(&mut self, offset: usize, word: U256) -> Result<(), VMError> {
326 let new_size: usize = offset
327 .checked_add(WORD_SIZE_IN_BYTES_USIZE)
328 .ok_or(OutOfBounds)?;
329
330 self.resize(new_size)?;
331 self.store(&u256_to_big_endian(word), offset, WORD_SIZE_IN_BYTES_USIZE)?;
332 Ok(())
333 }
334
335 #[inline]
340 pub fn resize_prerounded(&mut self, new_memory_size: usize) -> Result<(), VMError> {
341 debug_assert_eq!(new_memory_size % WORD_SIZE_IN_BYTES_USIZE, 0);
342 if new_memory_size == 0 {
343 return Ok(());
344 }
345 let current_len = self.len();
346 if new_memory_size <= current_len {
347 return Ok(());
348 }
349 self.len = new_memory_size;
350 let buffer = self.buf_mut();
351 #[allow(clippy::arithmetic_side_effects)]
352 let real_new_memory_size = new_memory_size + self.current_base;
353 if real_new_memory_size > buffer.len() {
354 let new_size = real_new_memory_size.next_multiple_of(64);
355 buffer.resize(new_size, 0);
356 }
357 Ok(())
358 }
359
360 pub fn store_word_prerounded(
364 &mut self,
365 offset: usize,
366 word: U256,
367 new_memory_size: usize,
368 ) -> Result<(), VMError> {
369 self.resize_prerounded(new_memory_size)?;
370 self.store(&u256_to_big_endian(word), offset, WORD_SIZE_IN_BYTES_USIZE)?;
371 Ok(())
372 }
373
374 pub fn load_word_prerounded(
378 &mut self,
379 offset: usize,
380 new_memory_size: usize,
381 ) -> Result<U256, VMError> {
382 self.resize_prerounded(new_memory_size)?;
383 let true_offset = offset.checked_add(self.current_base).ok_or(OutOfBounds)?;
384 let buf = self.buf();
385 #[allow(unsafe_code)]
387 let value: [u8; WORD_SIZE_IN_BYTES_USIZE] = unsafe {
388 *buf.get_unchecked(true_offset..(true_offset.wrapping_add(WORD_SIZE_IN_BYTES_USIZE)))
389 .as_ptr()
390 .cast::<[u8; WORD_SIZE_IN_BYTES_USIZE]>()
391 };
392 Ok(u256_from_big_endian_const(value))
393 }
394
395 pub fn copy_within(
399 &mut self,
400 from_offset: usize,
401 to_offset: usize,
402 size: usize,
403 ) -> Result<(), VMError> {
404 if size == 0 {
405 return Ok(());
406 }
407
408 self.resize(
409 to_offset
410 .max(from_offset)
411 .checked_add(size)
412 .ok_or(InternalError::Overflow)?,
413 )?;
414
415 let true_from_offset = from_offset
416 .checked_add(self.current_base)
417 .ok_or(OutOfBounds)?;
418
419 let true_to_offset = to_offset
420 .checked_add(self.current_base)
421 .ok_or(OutOfBounds)?;
422 let buffer = self.buf_mut();
423
424 buffer.copy_within(
425 true_from_offset
426 ..(true_from_offset
427 .checked_add(size)
428 .ok_or(InternalError::Overflow)?),
429 true_to_offset,
430 );
431
432 Ok(())
433 }
434
435 #[inline(always)]
436 pub fn store_zeros(&mut self, offset: usize, size: usize) -> Result<(), VMError> {
437 if size == 0 {
438 return Ok(());
439 }
440
441 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
442 self.resize(new_size)?;
443
444 let real_offset = self.current_base.wrapping_add(offset);
445 let buffer = self.buf_mut();
446
447 #[expect(unsafe_code)]
449 unsafe {
450 buffer
451 .get_unchecked_mut(real_offset..(real_offset.wrapping_add(size)))
452 .fill(0);
453 }
454
455 Ok(())
456 }
457}
458
459impl Default for Memory {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465#[inline]
468pub fn expansion_cost(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
469 let cost = if new_memory_size <= current_memory_size {
470 0
471 } else {
472 cost(new_memory_size)?.wrapping_sub(cost(current_memory_size)?)
475 };
476 Ok(cost)
477}
478
479#[inline]
482fn cost(memory_size: usize) -> Result<u64, VMError> {
483 let memory_size = u64::try_from(memory_size).map_err(|_| InternalError::TypeConversion)?;
484
485 let words = memory_size.div_ceil(WORD_SIZE_IN_BYTES_U64);
487
488 #[expect(clippy::arithmetic_side_effects)]
491 let gas_cost = words * words / MEMORY_EXPANSION_QUOTIENT + 3 * words;
492
493 Ok(gas_cost)
494}
495
496#[inline]
497pub fn calculate_memory_size(offset: usize, size: usize) -> Result<usize, VMError> {
498 if size == 0 {
499 return Ok(0);
500 }
501
502 offset
503 .checked_add(size)
504 .and_then(|sum| sum.checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE))
505 .ok_or(OutOfBounds.into())
506}