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)]
24pub enum CallData {
25 Owned(Bytes),
26 CallerMemory(CallerRange),
27}
28
29#[derive(Debug, Clone)]
32pub struct CallerRange {
33 buffer: Rc<RefCell<Vec<u8>>>,
34 start: usize,
36 len: usize,
37}
38
39impl Default for CallData {
40 fn default() -> Self {
41 Self::Owned(Bytes::new())
42 }
43}
44
45impl From<Bytes> for CallData {
46 fn from(bytes: Bytes) -> Self {
47 Self::Owned(bytes)
48 }
49}
50
51impl CallData {
52 #[inline]
53 pub fn len(&self) -> usize {
54 match self {
55 Self::Owned(bytes) => bytes.len(),
56 Self::CallerMemory(range) => range.len,
57 }
58 }
59
60 #[inline]
61 pub fn is_empty(&self) -> bool {
62 self.len() == 0
63 }
64
65 #[inline]
68 pub fn with_slice<R>(&self, f: impl FnOnce(&[u8]) -> R) -> Result<R, VMError> {
69 match self {
70 Self::Owned(bytes) => Ok(f(bytes)),
71 Self::CallerMemory(range) => {
72 let buffer = range.buffer.borrow();
73 let end = range.start.checked_add(range.len).ok_or(OutOfBounds)?;
74 let data = buffer.get(range.start..end).ok_or_else(|| {
77 InternalError::Custom("call data outside the memory buffer".to_owned())
78 })?;
79 Ok(f(data))
80 }
81 }
82 }
83
84 pub fn to_bytes(&self) -> Result<Bytes, VMError> {
87 match self {
88 Self::Owned(bytes) => Ok(bytes.clone()),
89 Self::CallerMemory(_) => self.with_slice(Bytes::copy_from_slice),
90 }
91 }
92
93 #[inline]
95 pub fn load_word(&self, offset: usize) -> Result<U256, VMError> {
96 self.with_slice(|data| {
97 let mut word = [0u8; WORD_SIZE_IN_BYTES_USIZE];
98 if let Some(tail) = data.get(offset..) {
99 let available = tail.len().min(WORD_SIZE_IN_BYTES_USIZE);
100 #[expect(clippy::indexing_slicing, reason = "available is within both slices")]
101 word[..available].copy_from_slice(&tail[..available]);
102 }
103 u256_from_big_endian_const(word)
104 })
105 }
106}
107
108#[derive(Debug, Clone)]
112pub struct Memory {
113 pub buffer: Rc<RefCell<Vec<u8>>>,
114 pub len: usize,
115 current_base: usize,
116}
117
118impl Memory {
119 #[inline]
120 pub fn new() -> Self {
121 Self {
122 buffer: Rc::new(RefCell::new(Vec::new())),
123 len: 0,
124 current_base: 0,
125 }
126 }
127
128 #[inline]
136 pub fn reset_for_reuse(&mut self) {
137 self.buf_mut().clear();
138 self.len = 0;
139 self.current_base = 0;
140 }
141
142 #[inline]
144 pub fn next_memory(&self) -> Memory {
145 let mut mem = self.clone();
146 mem.current_base = mem.buffer.borrow().len();
147 mem.len = 0;
148 mem
149 }
150
151 #[inline]
155 pub fn clean_from_base(&self) {
156 #[expect(unsafe_code)]
157 unsafe {
158 self.buffer
159 .borrow_mut()
160 .get_unchecked_mut(self.current_base..(self.current_base.wrapping_add(self.len)))
161 .fill(0);
162 }
163 }
164
165 #[cfg(target_arch = "riscv64")]
169 #[inline]
170 pub fn truncate_to_base(&self) {
171 self.buf_mut().truncate(self.current_base);
172 }
173
174 #[expect(unsafe_code, reason = "single active frame; no overlapping borrows")]
181 #[inline]
182 fn buf(&self) -> &Vec<u8> {
183 unsafe { &*self.buffer.as_ptr() }
184 }
185
186 #[expect(unsafe_code, reason = "single active frame; no overlapping borrows")]
188 #[expect(clippy::mut_from_ref, reason = "single active frame; see buf()")]
189 #[inline]
190 fn buf_mut(&self) -> &mut Vec<u8> {
191 unsafe { &mut *self.buffer.as_ptr() }
192 }
193
194 #[inline]
196 pub fn len(&self) -> usize {
197 self.len
198 }
199
200 #[inline]
201 pub fn is_empty(&self) -> bool {
202 self.len() == 0
203 }
204
205 pub fn live_bytes(&self) -> Vec<u8> {
208 if self.len == 0 {
209 return Vec::new();
210 }
211 let buf = self.buf();
212 let end = self.current_base.saturating_add(self.len);
213 buf.get(self.current_base..end)
214 .map(<[u8]>::to_vec)
215 .unwrap_or_default()
216 }
217
218 #[inline(always)]
222 pub fn resize(&mut self, new_memory_size: usize) -> Result<(), VMError> {
223 if new_memory_size == 0 {
224 return Ok(());
225 }
226
227 let new_memory_size = new_memory_size
228 .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE)
229 .ok_or(OutOfBounds)?;
230
231 let current_len = self.len();
232
233 if new_memory_size <= current_len {
234 return Ok(());
235 }
236
237 self.len = new_memory_size;
238
239 let buffer = self.buf_mut();
240
241 #[allow(clippy::arithmetic_side_effects)]
242 let real_new_memory_size = new_memory_size + self.current_base;
243
244 if real_new_memory_size > buffer.len() {
245 let new_size = real_new_memory_size.next_multiple_of(64);
247 buffer.resize(new_size, 0);
248 }
249
250 Ok(())
251 }
252
253 #[inline]
255 pub fn load_range(&mut self, offset: usize, size: usize) -> Result<Bytes, VMError> {
256 if size == 0 {
257 return Ok(Bytes::new());
258 }
259
260 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
261 self.resize(new_size)?;
262
263 let true_offset = offset.wrapping_add(self.current_base);
264
265 let buf = self.buf();
266
267 #[allow(unsafe_code)]
269 unsafe {
270 Ok(Bytes::copy_from_slice(buf.get_unchecked(
271 true_offset..(true_offset.wrapping_add(size)),
272 )))
273 }
274 }
275
276 #[inline]
280 pub fn calldata_view(&mut self, offset: usize, size: usize) -> Result<CallData, VMError> {
281 if size == 0 {
282 return Ok(CallData::default());
283 }
284
285 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
286 self.resize(new_size)?;
287
288 Ok(CallData::CallerMemory(CallerRange {
289 buffer: Rc::clone(&self.buffer),
290 start: self.current_base.checked_add(offset).ok_or(OutOfBounds)?,
291 len: size,
292 }))
293 }
294
295 pub fn store_calldata(
298 &mut self,
299 dst_offset: usize,
300 calldata: &CallData,
301 src_offset: usize,
302 size: usize,
303 ) -> Result<(), VMError> {
304 if size == 0 {
305 return Ok(());
306 }
307
308 let new_size = dst_offset.checked_add(size).ok_or(OutOfBounds)?;
310 self.resize(new_size)?;
311
312 let copied = calldata.len().saturating_sub(src_offset).min(size);
313 if copied > 0 {
314 match calldata {
315 CallData::CallerMemory(range) if Rc::ptr_eq(&range.buffer, &self.buffer) => {
316 let from = range.start.checked_add(src_offset).ok_or(OutOfBounds)?;
317 let to = self
318 .current_base
319 .checked_add(dst_offset)
320 .ok_or(OutOfBounds)?;
321 let from_end = from.checked_add(copied).ok_or(OutOfBounds)?;
322 let buffer = self.buf_mut();
323 if from_end > buffer.len() {
324 return Err(InternalError::Custom(
325 "call data outside the memory buffer".to_owned(),
326 )
327 .into());
328 }
329 buffer.copy_within(from..from_end, to);
330 }
331 _ => calldata.with_slice(|data| {
332 let data = data.get(src_offset..).unwrap_or_default();
333 self.store(data, dst_offset, copied)
334 })??,
335 }
336 }
337
338 if copied < size {
339 #[expect(clippy::arithmetic_side_effects, reason = "copied < size")]
340 self.store_zeros(dst_offset.wrapping_add(copied), size - copied)?;
341 }
342
343 Ok(())
344 }
345
346 #[inline]
355 pub fn with_range<R>(
356 &mut self,
357 offset: usize,
358 size: usize,
359 f: impl FnOnce(&[u8]) -> R,
360 ) -> Result<R, VMError> {
361 if size == 0 {
362 return Ok(f(&[]));
363 }
364
365 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
366 self.resize(new_size)?;
367
368 let true_offset = offset.wrapping_add(self.current_base);
369
370 let buf = self.buf();
371
372 #[allow(unsafe_code)]
374 let range = unsafe { buf.get_unchecked(true_offset..(true_offset.wrapping_add(size))) };
375 Ok(f(range))
376 }
377
378 #[inline(always)]
380 pub fn load_range_const<const N: usize>(&mut self, offset: usize) -> Result<[u8; N], VMError> {
381 let new_size = offset.checked_add(N).ok_or(OutOfBounds)?;
382 self.resize(new_size)?;
383
384 let true_offset = offset.checked_add(self.current_base).ok_or(OutOfBounds)?;
385
386 let buf = self.buf();
387 #[allow(unsafe_code)]
389 unsafe {
390 Ok(*buf
391 .get_unchecked(true_offset..(true_offset.wrapping_add(N)))
392 .as_ptr()
393 .cast::<[u8; N]>())
394 }
395 }
396
397 #[inline(always)]
399 pub fn load_word(&mut self, offset: usize) -> Result<U256, VMError> {
400 let value: [u8; 32] = self.load_range_const(offset)?;
401 Ok(u256_from_big_endian_const(value))
402 }
403
404 #[inline(always)]
408 fn store(&self, data: &[u8], at_offset: usize, data_size: usize) -> Result<(), VMError> {
409 if data_size == 0 {
410 return Ok(());
411 }
412
413 let real_offset = self.current_base.wrapping_add(at_offset);
414
415 let buffer = self.buf_mut();
416
417 let real_data_size = data_size.min(data.len());
418
419 #[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
421 #[allow(unsafe_code)]
422 unsafe {
423 std::ptr::copy_nonoverlapping(
424 data.get_unchecked(..real_data_size).as_ptr(),
425 buffer
426 .get_unchecked_mut(real_offset..(real_offset + real_data_size))
427 .as_mut_ptr(),
428 real_data_size,
429 );
430 }
431
432 Ok(())
433 }
434
435 #[inline(always)]
437 pub fn store_data(&mut self, offset: usize, data: &[u8]) -> Result<(), VMError> {
438 if data.is_empty() {
439 return Ok(());
440 }
441 let new_size = offset.checked_add(data.len()).ok_or(OutOfBounds)?;
442 self.resize(new_size)?;
443 self.store(data, offset, data.len())
444 }
445
446 #[inline(always)]
448 pub fn store_data_zero_padded(
449 &mut self,
450 offset: usize,
451 data: &[u8],
452 total_size: usize,
453 ) -> Result<(), VMError> {
454 if total_size == 0 {
455 return Ok(());
456 }
457
458 let new_size = offset.checked_add(total_size).ok_or(OutOfBounds)?;
459 self.resize(new_size)?;
460
461 let copy_size = data.len().min(total_size);
462 if copy_size > 0 {
463 self.store(data, offset, copy_size)?;
464 }
465
466 #[allow(clippy::arithmetic_side_effects)]
467 if copy_size < total_size {
468 let zero_offset = offset.wrapping_add(copy_size);
471 let zero_size = total_size - copy_size;
472 let real_offset = self.current_base.wrapping_add(zero_offset);
473 let buffer = self.buf_mut();
474
475 #[expect(unsafe_code)]
477 unsafe {
478 buffer
479 .get_unchecked_mut(real_offset..real_offset.wrapping_add(zero_size))
480 .fill(0);
481 }
482 }
483
484 Ok(())
485 }
486
487 #[inline(always)]
489 pub fn store_word(&mut self, offset: usize, word: U256) -> Result<(), VMError> {
490 let new_size: usize = offset
491 .checked_add(WORD_SIZE_IN_BYTES_USIZE)
492 .ok_or(OutOfBounds)?;
493
494 self.resize(new_size)?;
495 self.store(&u256_to_big_endian(word), offset, WORD_SIZE_IN_BYTES_USIZE)?;
496 Ok(())
497 }
498
499 #[inline]
504 pub fn resize_prerounded(&mut self, new_memory_size: usize) -> Result<(), VMError> {
505 debug_assert_eq!(new_memory_size % WORD_SIZE_IN_BYTES_USIZE, 0);
506 if new_memory_size == 0 {
507 return Ok(());
508 }
509 let current_len = self.len();
510 if new_memory_size <= current_len {
511 return Ok(());
512 }
513 self.len = new_memory_size;
514 let buffer = self.buf_mut();
515 #[allow(clippy::arithmetic_side_effects)]
516 let real_new_memory_size = new_memory_size + self.current_base;
517 if real_new_memory_size > buffer.len() {
518 let new_size = real_new_memory_size.next_multiple_of(64);
519 buffer.resize(new_size, 0);
520 }
521 Ok(())
522 }
523
524 pub fn store_word_prerounded(
528 &mut self,
529 offset: usize,
530 word: U256,
531 new_memory_size: usize,
532 ) -> Result<(), VMError> {
533 self.resize_prerounded(new_memory_size)?;
534 self.store(&u256_to_big_endian(word), offset, WORD_SIZE_IN_BYTES_USIZE)?;
535 Ok(())
536 }
537
538 pub fn load_word_prerounded(
542 &mut self,
543 offset: usize,
544 new_memory_size: usize,
545 ) -> Result<U256, VMError> {
546 self.resize_prerounded(new_memory_size)?;
547 let true_offset = offset.checked_add(self.current_base).ok_or(OutOfBounds)?;
548 let buf = self.buf();
549 #[allow(unsafe_code)]
551 let value: [u8; WORD_SIZE_IN_BYTES_USIZE] = unsafe {
552 *buf.get_unchecked(true_offset..(true_offset.wrapping_add(WORD_SIZE_IN_BYTES_USIZE)))
553 .as_ptr()
554 .cast::<[u8; WORD_SIZE_IN_BYTES_USIZE]>()
555 };
556 Ok(u256_from_big_endian_const(value))
557 }
558
559 pub fn copy_within(
563 &mut self,
564 from_offset: usize,
565 to_offset: usize,
566 size: usize,
567 ) -> Result<(), VMError> {
568 if size == 0 {
569 return Ok(());
570 }
571
572 self.resize(
573 to_offset
574 .max(from_offset)
575 .checked_add(size)
576 .ok_or(InternalError::Overflow)?,
577 )?;
578
579 let true_from_offset = from_offset
580 .checked_add(self.current_base)
581 .ok_or(OutOfBounds)?;
582
583 let true_to_offset = to_offset
584 .checked_add(self.current_base)
585 .ok_or(OutOfBounds)?;
586 let buffer = self.buf_mut();
587
588 buffer.copy_within(
589 true_from_offset
590 ..(true_from_offset
591 .checked_add(size)
592 .ok_or(InternalError::Overflow)?),
593 true_to_offset,
594 );
595
596 Ok(())
597 }
598
599 #[inline(always)]
600 pub fn store_zeros(&mut self, offset: usize, size: usize) -> Result<(), VMError> {
601 if size == 0 {
602 return Ok(());
603 }
604
605 let new_size = offset.checked_add(size).ok_or(OutOfBounds)?;
606 self.resize(new_size)?;
607
608 let real_offset = self.current_base.wrapping_add(offset);
609 let buffer = self.buf_mut();
610
611 #[expect(unsafe_code)]
613 unsafe {
614 buffer
615 .get_unchecked_mut(real_offset..(real_offset.wrapping_add(size)))
616 .fill(0);
617 }
618
619 Ok(())
620 }
621}
622
623impl Default for Memory {
624 fn default() -> Self {
625 Self::new()
626 }
627}
628
629#[inline]
632pub fn expansion_cost(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
633 let cost = if new_memory_size <= current_memory_size {
634 0
635 } else {
636 cost(new_memory_size)?.wrapping_sub(cost(current_memory_size)?)
639 };
640 Ok(cost)
641}
642
643#[inline]
646fn cost(memory_size: usize) -> Result<u64, VMError> {
647 let memory_size = u64::try_from(memory_size).map_err(|_| InternalError::TypeConversion)?;
648
649 let words = memory_size.div_ceil(WORD_SIZE_IN_BYTES_U64);
651
652 #[expect(clippy::arithmetic_side_effects)]
655 let gas_cost = words * words / MEMORY_EXPANSION_QUOTIENT + 3 * words;
656
657 Ok(gas_cost)
658}
659
660#[inline]
661pub fn calculate_memory_size(offset: usize, size: usize) -> Result<usize, VMError> {
662 if size == 0 {
663 return Ok(0);
664 }
665
666 offset
667 .checked_add(size)
668 .and_then(|sum| sum.checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE))
669 .ok_or(OutOfBounds.into())
670}