Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/eval/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub fn mload(state: &mut Machine) -> Control {
pop_u256!(state, index);
let index = as_usize_or_fail!(index);
try_or_fail!(state.memory.resize_offset(index, 32));
let value = H256::from_slice(&state.memory.get(index, 32)[..]);
let value = state.memory.load_h256(index);
push!(state, value);
Control::Continue(1)
}
Expand Down
22 changes: 20 additions & 2 deletions core/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ pub struct Memory {
impl Memory {
/// Create a new memory with the given limit.
#[must_use]
pub const fn new(limit: usize) -> Self {
pub fn new(limit: usize) -> Self {
Self {
data: Vec::new(),
// Modest reserve: cut realloc-copy as memory grows via `set()` resize.
data: Vec::with_capacity(1024),
effective_len: 0_usize,
limit,
}
Expand Down Expand Up @@ -110,6 +111,23 @@ impl Memory {
ret
}

/// Read a 32-byte word at `offset` directly into an `H256`, no heap Vec.
/// Behaviour-identical to `H256::from_slice(&self.get(offset, 32))` (zero-pad
/// out of range / past data) — avoids the per-MLOAD `vec![0; 32]` allocation.
#[must_use]
pub fn load_h256(&self, offset: usize) -> crate::H256 {
let mut ret = crate::H256::default();
if offset >= self.data.len() {
return ret;
}
if offset.checked_add(32).map_or(true, |pos| pos > self.limit) {
return ret;
}
let end = min(offset + 32, self.data.len());
ret[0..(end - offset)].copy_from_slice(&self.data[offset..end]);
ret
}

/// Set memory region at given offset. The offset and value is considered
/// untrusted.
pub fn set(
Expand Down
6 changes: 4 additions & 2 deletions core/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ pub struct Stack {
impl Stack {
/// Create a new stack with given limit.
#[must_use]
pub const fn new(limit: usize) -> Self {
pub fn new(limit: usize) -> Self {
Self {
data: Vec::new(),
// Modest reserve: kill geometric realloc-copy on the hot PUSH path
// (PUSH ≈ 28% of swap CU) without the full-limit (32KB/frame) heap blowup.
data: Vec::with_capacity(64),
limit,
}
}
Expand Down
77 changes: 77 additions & 0 deletions core/tests/load_h256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Differential test for `Memory::load_h256` — the no-allocation MLOAD path.
//!
//! `load_h256(offset)` must be BIT-IDENTICAL to the prior MLOAD implementation
//! `H256::from_slice(&memory.get(offset, 32)[..])` for every input, since MLOAD
//! is consensus-critical. This exercises the interpreter `Memory` directly (no
//! contract / no swap) across every boundary: empty memory, reads past the
//! written region (zero-pad), partial words at the tail, exact-fit, and the
//! `offset + 32 > limit` guard. Run: `RUSTFLAGS=-Aunexpected_cfgs cargo test`.

use evm_core::{Memory, H256};

/// The exact expression MLOAD used before `load_h256` existed.
fn reference(mem: &Memory, offset: usize) -> H256 {
H256::from_slice(&mem.get(offset, 32)[..])
}

fn assert_equiv(mem: &Memory, offset: usize) {
assert_eq!(
mem.load_h256(offset),
reference(mem, offset),
"load_h256 diverged from get()+from_slice at offset {offset}"
);
}

#[test]
fn load_h256_matches_reference_across_boundaries() {
let limit = 1024 * 1024;
let mut mem = Memory::new(limit);

// Write a recognizable, non-zero pattern over [0, 200): byte i = i+1.
let pattern: Vec<u8> = (0u16..200).map(|i| (i % 251 + 1) as u8).collect();
mem.set(0, &pattern, Some(pattern.len())).unwrap();

// Cover: word-aligned, mid-word, the written/zero boundary, partial tail,
// fully past data, and large offsets near the limit.
let offsets = [
0, 1, 2, 31, 32, 33, 63, 64, 95,
168, 169, 170, 199, 200, 201, // 200 = data.len(): tail / past-end edge
255, 256, 1000, 4095, 4096,
limit - 32, limit - 1, limit, limit + 1,
];
for off in offsets {
assert_equiv(&mem, off);
}
}

#[test]
fn load_h256_on_empty_memory_is_zero() {
let mem = Memory::new(1024 * 1024);
assert_eq!(mem.load_h256(0), H256::default());
assert_eq!(mem.load_h256(0), reference(&mem, 0));
assert_eq!(mem.load_h256(64), reference(&mem, 64));
}

#[test]
fn load_h256_partial_tail_word_is_zero_padded() {
// Memory written to exactly 40 bytes; reading a 32-byte word at offset 16
// straddles the written/unwritten boundary (16..40 written, 40..48 zero).
let mut mem = Memory::new(1024 * 1024);
let data: Vec<u8> = (0u8..40).map(|i| i + 1).collect();
mem.set(0, &data, Some(40)).unwrap();
for off in 0..=48 {
assert_equiv(&mem, off);
}
}

#[test]
fn load_h256_respects_limit_guard() {
// Small limit: any read whose window crosses `limit` must return zeros,
// identically to `get`.
let limit = 96usize;
let mut mem = Memory::new(limit);
mem.set(0, &[0xABu8; 64], Some(64)).unwrap();
for off in [0, 32, 63, 64, 65, 96, 97, usize::MAX - 16] {
assert_equiv(&mem, off);
}
}