Skip to content

fix(cast): disassembler PC & end of code push padding #10520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 15, 2025
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
13 changes: 9 additions & 4 deletions crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2387,16 +2387,21 @@ mod tests {
#[test]
fn disassemble_incomplete_sequence() {
let incomplete = &hex!("60"); // PUSH1
let disassembled = Cast::disassemble(incomplete);
assert!(disassembled.is_err());
let disassembled = Cast::disassemble(incomplete).unwrap();
assert_eq!(disassembled, "00000000: PUSH1 0x00\n");

let complete = &hex!("6000"); // PUSH1 0x00
let disassembled = Cast::disassemble(complete);
assert!(disassembled.is_ok());

let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes
let disassembled = Cast::disassemble(incomplete);
assert!(disassembled.is_err());

let disassembled = Cast::disassemble(incomplete).unwrap();

assert_eq!(
disassembled,
"00000000: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n"
);

let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes
let disassembled = Cast::disassemble(complete);
Expand Down
24 changes: 16 additions & 8 deletions crates/evm/core/src/ic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,34 +98,42 @@ fn make_map<const PC_FIRST: bool>(code: &[u8]) -> FxHashMap<u32, u32> {
}

/// Represents a single instruction consisting of the opcode and its immediate data.
pub struct Instruction<'a> {
pub struct Instruction {
/// OpCode, if it could be decoded.
pub op: Option<OpCode>,
/// Immediate data following the opcode.
pub immediate: &'a [u8],
pub immediate: Box<[u8]>,
/// Program counter of the opcode.
pub pc: u32,
}

/// Decodes raw opcode bytes into [`Instruction`]s.
pub fn decode_instructions(code: &[u8]) -> Result<Vec<Instruction<'_>>> {
pub fn decode_instructions(code: &[u8]) -> Result<Vec<Instruction>> {
assert!(code.len() <= u32::MAX as usize, "bytecode is too big");

let mut pc = 0usize;
let mut steps = Vec::new();

while pc < code.len() {
let op = OpCode::new(code[pc]);
pc += 1;
let immediate_size = op.map(|op| immediate_size(op, &code[pc..])).unwrap_or(0) as usize;
let next_pc = pc + 1;
let immediate_size =
op.map(|op| immediate_size(op, &code[next_pc..])).unwrap_or(0) as usize;
let is_normal_push = op.map(|op| op.is_push()).unwrap_or(false);

if pc + immediate_size > code.len() {
if !is_normal_push && next_pc + immediate_size > code.len() {
eyre::bail!("incomplete sequence of bytecode");
}

steps.push(Instruction { op, pc: pc as u32, immediate: &code[pc..pc + immediate_size] });
// Ensure immediate is padded if needed.
let immediate_end = (next_pc + immediate_size).min(code.len());
let mut immediate = vec![0u8; immediate_size];
let immediate_part = &code[next_pc..immediate_end];
immediate[..immediate_part.len()].copy_from_slice(immediate_part);

steps.push(Instruction { op, pc: pc as u32, immediate: immediate.into_boxed_slice() });

pc += immediate_size;
pc = next_pc + immediate_size;
}

Ok(steps)
Expand Down