Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/l2/prover/src/guest_program/src/risc0/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/l2/prover/src/guest_program/src/sp1/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion crates/vm/levm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ sha3 = "0.10.8"
datatest-stable = "0.2.9"
walkdir = "2.5.0"
secp256k1.workspace = true
p256 = { version = "0.13.2", features = ["ecdsa", "arithmetic", "expose-field"] }
p256 = { version = "0.13.2", features = [
"ecdsa",
"arithmetic",
"expose-field",
] }
sha2 = "0.10.8"
ripemd = "0.1.3"
malachite = "0.6.1"
Expand All @@ -39,6 +43,7 @@ ark-ff = "0.5.0"
strum = { version = "0.27.1", features = ["derive"] }
k256 = "0.13.4"

bitvec = { version = "1.0.1", features = ["alloc"] }

[dev-dependencies]
hex.workspace = true
Expand Down
79 changes: 39 additions & 40 deletions crates/vm/levm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use crate::{
vm::{Substate, VM},
};
use ExceptionalHalt::OutOfGas;
use bytes::{Bytes, buf::IntoIter};
use bitvec::{bitvec, order::Msb0, vec::BitVec};
use bytes::Bytes;
use ethrex_common::{
Address, H256, U256,
evm::calculate_create_address,
Expand All @@ -29,7 +30,7 @@ use secp256k1::{
ecdsa::{RecoverableSignature, RecoveryId},
};
use sha3::{Digest, Keccak256};
use std::{collections::HashMap, iter::Enumerate};
use std::collections::HashMap;
pub type Storage = HashMap<U256, H256>;

// ================== Address related functions ======================
Expand Down Expand Up @@ -79,59 +80,57 @@ pub fn calculate_create2_address(
/// offsets of bytes `0x5B` (`JUMPDEST`) within push constants.
#[derive(Debug)]
pub struct JumpTargetFilter {
/// The list of invalid jump target offsets.
filter: Vec<usize>,
/// The last processed offset, plus one.
offset: usize,

/// Program bytecode iterator.
iter: Enumerate<IntoIter<Bytes>>,
/// Number of bytes remaining to process from the last push instruction.
partial: usize,
bytecode: Bytes,
jumpdests: Option<BitVec<u8, Msb0>>,
}

impl JumpTargetFilter {
/// Create an empty `JumpTargetFilter`.
pub fn new(bytecode: Bytes) -> Self {
Self {
filter: Vec::new(),
offset: 0,

iter: bytecode.into_iter().enumerate(),
partial: 0,
bytecode,
jumpdests: None,
}
}

/// Check whether a target jump address is blacklisted or not.
///
/// This method may potentially grow the filter if the requested address is out of range.
/// Builds the jumpdest table on the first call, and caches it for future calls.
#[expect(
clippy::as_conversions,
clippy::arithmetic_side_effects,
clippy::indexing_slicing
)]
pub fn is_blacklisted(&mut self, address: usize) -> bool {
if let Some(delta) = address.checked_sub(self.offset) {
// It is not realistic to expect a bytecode offset to overflow an `usize`.
#[expect(clippy::arithmetic_side_effects)]
for (offset, value) in (&mut self.iter).take(delta + 1) {
match self.partial.checked_sub(1) {
None => {
// Neither the `as` conversions nor the subtraction can fail here.
#[expect(clippy::as_conversions)]
if (Opcode::PUSH1..=Opcode::PUSH32).contains(&Opcode::from(value)) {
self.partial = value as usize - Opcode::PUSH0 as usize;
}
}
Some(partial) => {
self.partial = partial;

#[expect(clippy::as_conversions)]
if value == Opcode::JUMPDEST as u8 {
self.filter.push(offset);
}
match self.jumpdests {
// Already built the jumpdest table, just check it
Some(ref jumpdests) => address >= jumpdests.len() || !jumpdests[address],
// First time we are called, need to build the jumpdest table
None => {
let code = &self.bytecode;
let len = code.len();
let mut jumpdests = bitvec![u8, Msb0; 0; len]; // All false, size = len

let mut i = 0;
while i < len {

let opcode = Opcode::from(code[i]);
if opcode == Opcode::JUMPDEST {
jumpdests.set(i, true);
} else if (Opcode::PUSH1..=Opcode::PUSH32).contains(&opcode) {
// PUSH1 (0x60) to PUSH32 (0x7f): skip 1 to 32 bytes
let skip = opcode as usize - Opcode::PUSH0 as usize;
Copy link
Preview

Copilot AI Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skip calculation is incorrect. For PUSH1 through PUSH32, the number of data bytes to skip should be opcode as usize - Opcode::PUSH1 as usize + 1. Currently, using PUSH0 as the base means PUSH1 skips 1 byte, PUSH2 skips 2 bytes, etc., but it should skip 1, 2, 3... up to 32 bytes respectively.

Suggested change
let skip = opcode as usize - Opcode::PUSH0 as usize;
let skip = opcode as usize - Opcode::PUSH1 as usize + 1;

Copilot uses AI. Check for mistakes.

i += skip; // Advance past data bytes
}
i += 1;
}
}

self.filter.last() == Some(&address)
} else {
self.filter.binary_search(&address).is_ok()
let is_blacklisted = address >= jumpdests.len() || !jumpdests[address];

self.jumpdests = Some(jumpdests);

is_blacklisted
}
}
}
}
Expand Down
Loading