aurora_evm/core/
memory.rs

1use super::prelude::*;
2use super::utils::USIZE_MAX;
3use crate::{ExitError, ExitFatal};
4use core::cmp::min;
5use core::ops::{BitAnd, Not};
6use primitive_types::{H256, U256};
7
8/// A sequential memory. It uses Rust's `Vec` for internal
9/// representation.
10#[derive(Clone, Debug)]
11pub struct Memory {
12    /// Memory data
13    data: Vec<u8>,
14    /// Memory effective length, that changed after resize operations.
15    effective_len: usize,
16    /// Memory limit
17    limit: usize,
18}
19
20impl Memory {
21    /// Create a new memory with the given limit.
22    #[must_use]
23    pub const fn new(limit: usize) -> Self {
24        Self {
25            data: Vec::new(),
26            effective_len: 0,
27            limit,
28        }
29    }
30
31    /// Memory limit.
32    #[must_use]
33    pub const fn limit(&self) -> usize {
34        self.limit
35    }
36
37    /// Get the length of the current memory range.
38    #[must_use]
39    // TODO: rust-v1.87 - const fn
40    #[allow(clippy::missing_const_for_fn)]
41    pub fn len(&self) -> usize {
42        self.data.len()
43    }
44
45    /// Get the effective length.
46    #[must_use]
47    pub const fn effective_len(&self) -> usize {
48        self.effective_len
49    }
50
51    /// Return true if current effective memory range is zero.
52    #[must_use]
53    // TODO: rust-v1.87 - const fn
54    #[allow(clippy::missing_const_for_fn)]
55    pub fn is_empty(&self) -> bool {
56        self.len() == 0
57    }
58
59    /// Return the full memory.
60    #[must_use]
61    pub const fn data(&self) -> &Vec<u8> {
62        &self.data
63    }
64
65    /// Resize the memory, making it cover the memory region of `offset..offset + len`,
66    /// with 32 bytes as the step. If the length is zero, this function does nothing.
67    ///
68    /// # Errors
69    /// Return `ExitError::InvalidRange` if `offset + len` is overflow.
70    pub fn resize_offset(&mut self, offset: usize, len: usize) -> Result<(), ExitError> {
71        if len == 0 {
72            return Ok(());
73        }
74
75        offset
76            .checked_add(len)
77            .map_or(Err(ExitError::InvalidRange), |end| self.resize_end(end))
78    }
79
80    /// Resize the memory, making it cover to `end`, with 32 bytes as the step.
81    ///
82    /// # Errors
83    /// Return `ExitError::InvalidRange` if `end` value is overflow in `next_multiple_of_32` call.
84    pub fn resize_end(&mut self, end: usize) -> Result<(), ExitError> {
85        if end > self.effective_len {
86            let new_end = next_multiple_of_32(end).ok_or(ExitError::InvalidRange)?;
87            self.effective_len = new_end;
88        }
89
90        Ok(())
91    }
92
93    /// Get memory region at given offset.
94    ///
95    /// ## Panics
96    ///
97    /// Value of `size` is considered trusted. If they're too large,
98    /// the program can run out of memory, or it can overflow.
99    #[must_use]
100    pub fn get(&self, mut offset: usize, size: usize) -> Vec<u8> {
101        if offset > self.data.len() {
102            offset = self.data.len();
103        }
104
105        let mut end = offset + size;
106        if end > self.data.len() {
107            end = self.data.len();
108        }
109
110        let mut ret = self.data[offset..end].to_vec();
111        ret.resize(size, 0);
112        ret
113    }
114
115    /// Get `H256` value from a specific offset in memory.
116    #[must_use]
117    pub fn get_h256(&self, offset: usize) -> H256 {
118        let mut ret = [0; 32];
119
120        let data_len = self.data.len();
121        if offset >= data_len {
122            return H256(ret);
123        }
124        let available_bytes = data_len - offset;
125        let count = 32.min(available_bytes);
126        ret[..count].copy_from_slice(&self.data[offset..offset + count]);
127
128        H256(ret)
129    }
130
131    /// Set memory region at given offset. The offset and value is considered
132    /// untrusted.
133    ///
134    /// # Errors
135    /// Return `ExitFatal::NotSupported` if `offset + target_size` is out of memory limit or overflow.
136    pub fn set(
137        &mut self,
138        offset: usize,
139        value: &[u8],
140        target_size: usize,
141    ) -> Result<(), ExitFatal> {
142        if target_size == 0 {
143            return Ok(());
144        }
145
146        let end_offset = match offset.checked_add(target_size) {
147            Some(pos) if pos <= self.limit => pos,
148            _ => return Err(ExitFatal::NotSupported),
149        };
150
151        if self.data.len() < end_offset {
152            self.data.resize(end_offset, 0);
153        }
154
155        let copy_len = min(value.len(), target_size);
156        let dest_slice = &mut self.data[offset..end_offset];
157        if copy_len > 0 {
158            dest_slice[..copy_len].copy_from_slice(&value[..copy_len]);
159        }
160
161        if target_size > copy_len {
162            dest_slice[copy_len..].fill(0);
163        }
164
165        Ok(())
166    }
167
168    /// Copy memory region form `src` to `dst` with length.
169    /// `copy_within` uses `memmove` to avoid `DoS` attacks.
170    ///
171    /// # Errors
172    /// Return `ExitFatal::Other`:
173    /// - `OverflowOnCopy` if `offset + length` is overflow
174    /// - `OutOfGasOnCopy` if `offst_length` out of memory limit
175    pub fn copy(
176        &mut self,
177        src_offset: usize,
178        dst_offset: usize,
179        length: usize,
180    ) -> Result<(), ExitFatal> {
181        // If length is zero - do nothing
182        if length == 0 {
183            return Ok(());
184        }
185
186        // Get maximum offset
187        let offset = core::cmp::max(src_offset, dst_offset);
188        let offset_length = offset
189            .checked_add(length)
190            .ok_or_else(|| ExitFatal::Other(Cow::from("OverflowOnCopy")))?;
191        if offset_length > self.limit {
192            return Err(ExitFatal::Other(Cow::from("OutOfGasOnCopy")));
193        }
194
195        // Resize data memory
196        if self.data.len() < offset_length {
197            self.data.resize(offset_length, 0);
198        }
199
200        self.data
201            .copy_within(src_offset..src_offset + length, dst_offset);
202        Ok(())
203    }
204
205    /// Copy `data` into the memory, for the given `length`.
206    ///
207    /// Copies `min(length, available_source_bytes)` from the source `data`
208    /// starting at `data_offset`, into `self.data` starting at `memory_offset`.
209    /// If `length` is greater than the number of bytes copied from source, the
210    /// remaining bytes in the destination range (up to `length`) are filled with zeros.
211    ///
212    /// # Errors
213    /// Returns `ExitFatal::NotSupported` if the destination range `memory_offset..memory_offset + length`
214    /// exceeds the memory limit or causes usize overflow.
215    pub fn copy_data(
216        &mut self,
217        memory_offset: usize,
218        data_offset: U256,
219        length: usize,
220        data: &[u8],
221    ) -> Result<(), ExitFatal> {
222        // 1. Handle zero length copy (no-op)
223        if length == 0 {
224            return Ok(());
225        }
226
227        // 2. Check destination bounds and calculate end offset
228        let dest_end_offset = match memory_offset.checked_add(length) {
229            Some(pos) if pos <= self.limit => pos,
230            _ => return Err(ExitFatal::NotSupported), // Error if overflow or exceeds limit
231        };
232
233        // 3. Ensure destination buffer (`self.data`) is large enough
234        //    Resize before taking mutable slices.
235        if self.data.len() < dest_end_offset {
236            self.data.resize(dest_end_offset, 0);
237        }
238
239        // 4. Preparing the copy and padding directly into self.data
240        // Get the mutable slice of the exact destination region length
241        // This is safe because we resized self.data to at least `dest_end_offset`
242        let dest_slice = &mut self.data[memory_offset..dest_end_offset];
243
244        // 5. Check source bounds and rethink zero the data slice
245        if data_offset > USIZE_MAX {
246            dest_slice.fill(0);
247            return Ok(());
248        }
249        let data_offset = data_offset.as_usize();
250        if data_offset > data.len() {
251            dest_slice.fill(0);
252            return Ok(());
253        }
254        // Calculate how many bytes are available in `data` from `data_offset`
255        let actual_len = data.len() - data_offset;
256        // Calculate copy length as the minimum of requested length and available length
257        let copy_len = min(actual_len, length);
258        // Copy data to `dest_slice`
259        if copy_len > 0 {
260            dest_slice[..copy_len].copy_from_slice(&data[data_offset..data_offset + copy_len]);
261        }
262        if length > copy_len {
263            dest_slice[copy_len..].fill(0);
264        }
265        Ok(())
266    }
267}
268
269/// Rounds up `x` to the closest multiple of 32. If `x % 32 == 0` then `x` is returned.
270#[inline]
271fn next_multiple_of_32(x: usize) -> Option<usize> {
272    let r = x.bitand(31).not().wrapping_add(1).bitand(31);
273    x.checked_add(r)
274}
275
276#[cfg(test)]
277mod tests {
278    use super::next_multiple_of_32;
279
280    #[test]
281    fn test_next_multiple_of_32() {
282        // next_multiple_of_32 returns x when it is a multiple of 32
283        for i in 0..32 {
284            let x = i * 32;
285            assert_eq!(Some(x), next_multiple_of_32(x));
286        }
287
288        // next_multiple_of_32 rounds up to the nearest multiple of 32 when `x % 32 != 0`
289        for x in 0..1024 {
290            if x % 32 == 0 {
291                continue;
292            }
293            let next_multiple = x + 32 - (x % 32);
294            assert_eq!(Some(next_multiple), next_multiple_of_32(x));
295        }
296
297        // next_multiple_of_32 returns None when the next multiple of 32 is too big
298        let last_multiple_of_32 = usize::MAX & !31;
299        for i in 0..63 {
300            let x = usize::MAX - i;
301            if x > last_multiple_of_32 {
302                assert_eq!(None, next_multiple_of_32(x));
303            } else {
304                assert_eq!(Some(last_multiple_of_32), next_multiple_of_32(x));
305            }
306        }
307    }
308}