diff --git a/crates/bytecode/src/legacy/analysis.rs b/crates/bytecode/src/legacy/analysis.rs index 39e4a1e1d0..e06ee173d3 100644 --- a/crates/bytecode/src/legacy/analysis.rs +++ b/crates/bytecode/src/legacy/analysis.rs @@ -29,13 +29,16 @@ pub fn analyze_legacy(bytecode: Bytes) -> (JumpTable, Bytes) { iterator = unsafe { iterator.add(1) }; } else { let push_offset = opcode.wrapping_sub(opcode::PUSH1); - if push_offset < 32 { - // SAFETY: Iterator access range is checked in the while loop - iterator = unsafe { iterator.add(push_offset as usize + 2) }; + let dupn_offset = opcode.wrapping_sub(opcode::DUPN); + let skip = if push_offset < 32 { + 2 + push_offset as usize + } else if dupn_offset < 3 { + 2 } else { - // SAFETY: Iterator access range is checked in the while loop - iterator = unsafe { iterator.add(1) }; - } + 1 + }; + // SAFETY: Iterator access range is checked in the while loop + iterator = unsafe { iterator.add(skip) }; } } diff --git a/crates/bytecode/src/opcode.rs b/crates/bytecode/src/opcode.rs index 4f1d4247f7..cc1fb61f98 100644 --- a/crates/bytecode/src/opcode.rs +++ b/crates/bytecode/src/opcode.rs @@ -623,9 +623,9 @@ opcodes! { // 0xE3 // 0xE4 // 0xE5 - // 0xE6 - // 0xE7 - // 0xE8 + 0xE6 => DUPN => stack_io(0, 1), immediate_size(1); + 0xE7 => SWAPN => stack_io(0, 0), immediate_size(1); + 0xE8 => EXCHANGE => stack_io(0, 0), immediate_size(1); // 0xE9 // 0xEA // 0xEB @@ -668,11 +668,15 @@ mod tests { #[test] fn test_immediate_size() { let mut expected = [0u8; 256]; - // PUSH opcodes + for push in PUSH1..=PUSH32 { expected[push as usize] = push - PUSH1 + 1; } + for stack_op in [DUPN, SWAPN, EXCHANGE] { + expected[stack_op as usize] = 1; + } + for (i, opcode) in OPCODE_INFO.iter().enumerate() { if let Some(opcode) = opcode { assert_eq!( @@ -718,7 +722,7 @@ mod tests { for _ in OPCODE_INFO.into_iter().flatten() { opcode_num += 1; } - assert_eq!(opcode_num, 150); + assert_eq!(opcode_num, 153); } #[test] diff --git a/crates/interpreter/src/instruction_result.rs b/crates/interpreter/src/instruction_result.rs index 74b295e64d..b7cdedaba5 100644 --- a/crates/interpreter/src/instruction_result.rs +++ b/crates/interpreter/src/instruction_result.rs @@ -81,6 +81,8 @@ pub enum InstructionResult { CreateInitCodeSizeLimit, /// Fatal external error. Returned by database. FatalExternalError, + /// Invalid encoding of an instruction's immediate operand. + InvalidImmediateEncoding, } impl From for InstructionResult { @@ -190,6 +192,7 @@ macro_rules! return_error { | $crate::InstructionResult::CreateContractStartingWithEF | $crate::InstructionResult::CreateInitCodeSizeLimit | $crate::InstructionResult::FatalExternalError + | $crate::InstructionResult::InvalidImmediateEncoding }; } @@ -348,6 +351,9 @@ impl> From for SuccessOrHalt { Self::Internal(InternalResult::InvalidExtDelegateCallTarget) } + InstructionResult::InvalidImmediateEncoding => { + Self::Halt(HaltReason::OpcodeNotFound.into()) + } } } } diff --git a/crates/interpreter/src/instructions.rs b/crates/interpreter/src/instructions.rs index cae338c29f..ebde810b5b 100644 --- a/crates/interpreter/src/instructions.rs +++ b/crates/interpreter/src/instructions.rs @@ -278,6 +278,10 @@ const fn instruction_table_impl() -> [Instructi table[SWAP15 as usize] = Instruction::new(stack::swap::<15, _, _>, 3); table[SWAP16 as usize] = Instruction::new(stack::swap::<16, _, _>, 3); + table[DUPN as usize] = Instruction::new(stack::dupn, 3); + table[SWAPN as usize] = Instruction::new(stack::swapn, 3); + table[EXCHANGE as usize] = Instruction::new(stack::exchange, 3); + table[LOG0 as usize] = Instruction::new(host::log::<0, _>, gas::LOG); table[LOG1 as usize] = Instruction::new(host::log::<1, _>, gas::LOG); table[LOG2 as usize] = Instruction::new(host::log::<2, _>, gas::LOG); diff --git a/crates/interpreter/src/instructions/stack.rs b/crates/interpreter/src/instructions/stack.rs index f2fb7f4cba..54f9fbf7c3 100644 --- a/crates/interpreter/src/instructions/stack.rs +++ b/crates/interpreter/src/instructions/stack.rs @@ -34,7 +34,6 @@ pub fn push( return; } - // Can ignore return. as relative N jump is safe operation context.interpreter.bytecode.relative_jump(N as isize); } @@ -60,3 +59,176 @@ pub fn swap( context.interpreter.halt(InstructionResult::StackOverflow); } } + +/// Implements the DUPN instruction. +/// +/// Duplicates the Nth stack item to the top of the stack, with N given by an immediate. +pub fn dupn(context: InstructionContext<'_, H, WIRE>) { + check!(context.interpreter, AMSTERDAM); + let x: usize = context.interpreter.bytecode.read_u8().into(); + if let Some(n) = decode_single(x) { + if !context.interpreter.stack.dup(n) { + context.interpreter.halt(InstructionResult::StackOverflow); + } + context.interpreter.bytecode.relative_jump(1); + } else { + context + .interpreter + .halt(InstructionResult::InvalidImmediateEncoding); + } +} + +/// Implements the SWAPN instruction. +/// +/// Swaps the top stack item with the N+1th stack item, with N given by an immediate. +pub fn swapn(context: InstructionContext<'_, H, WIRE>) { + check!(context.interpreter, AMSTERDAM); + let x: usize = context.interpreter.bytecode.read_u8().into(); + if let Some(n) = decode_single(x) { + if !context.interpreter.stack.exchange(0, n) { + context.interpreter.halt(InstructionResult::StackOverflow); + } + context.interpreter.bytecode.relative_jump(1); + } else { + context + .interpreter + .halt(InstructionResult::InvalidImmediateEncoding); + } +} + +/// Implements the EXCHANGE instruction. +/// +/// Swaps the N+1th stack item with the M+1th stack item, with N, M given by an immediate. +pub fn exchange(context: InstructionContext<'_, H, WIRE>) { + check!(context.interpreter, AMSTERDAM); + let x: usize = context.interpreter.bytecode.read_u8().into(); + if let Some((n, m)) = decode_pair(x) { + if !context.interpreter.stack.exchange(n, m - n) { + context.interpreter.halt(InstructionResult::StackOverflow); + } + context.interpreter.bytecode.relative_jump(1); + } else { + context + .interpreter + .halt(InstructionResult::InvalidImmediateEncoding); + } +} + +fn decode_single(x: usize) -> Option { + if x <= 90 { + Some(x + 17) + } else if x >= 128 { + Some(x - 20) + } else { + None + } +} + +fn decode_pair(x: usize) -> Option<(usize, usize)> { + let k = if x <= 79 { + x + } else if x >= 128 { + x - 48 + } else { + return None; + }; + let q = k / 16; + let r = k % 16; + if q < r { + Some((q + 1, r + 1)) + } else { + Some((r + 1, 29 - q)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + gas::params::GasParams, + host::DummyHost, + instructions::instruction_table, + interpreter::{EthInterpreter, ExtBytecode, InputsImpl, SharedMemory}, + interpreter_types::LoopControl, + Interpreter, + }; + use bytecode::opcode::*; + use bytecode::Bytecode; + use primitives::{hardfork::SpecId, Bytes, U256}; + + fn run_bytecode(code: &[u8]) -> Interpreter { + let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(code)); + let mut interpreter = Interpreter::::new( + SharedMemory::new(), + ExtBytecode::new(bytecode), + InputsImpl::default(), + false, + SpecId::AMSTERDAM, + u64::MAX, + GasParams::default(), + ); + let table = instruction_table::(); + let mut host = DummyHost; + interpreter.run_plain(&table, &mut host); + interpreter + } + + #[test] + fn test_dupn() { + let interpreter = run_bytecode(&[ + PUSH1, 0x01, PUSH1, 0x00, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, + DUP1, DUP1, DUP1, DUP1, DUP1, DUPN, 0x00, + ]); + assert_eq!(interpreter.stack.len(), 18); + assert_eq!(interpreter.stack.data()[17], U256::from(1)); + assert_eq!(interpreter.stack.data()[0], U256::from(1)); + for i in 1..17 { + assert_eq!(interpreter.stack.data()[i], U256::ZERO); + } + } + + #[test] + fn test_swapn() { + let interpreter = run_bytecode(&[ + PUSH1, 0x01, PUSH1, 0x00, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, DUP1, + DUP1, DUP1, DUP1, DUP1, DUP1, PUSH1, 0x02, SWAPN, 0x00, + ]); + assert_eq!(interpreter.stack.len(), 18); + assert_eq!(interpreter.stack.data()[17], U256::from(1)); + assert_eq!(interpreter.stack.data()[0], U256::from(2)); + for i in 1..17 { + assert_eq!(interpreter.stack.data()[i], U256::ZERO); + } + } + + #[test] + fn test_exchange() { + let interpreter = run_bytecode(&[PUSH1, 0x00, PUSH1, 0x01, PUSH1, 0x02, EXCHANGE, 0x01]); + assert_eq!(interpreter.stack.len(), 3); + assert_eq!(interpreter.stack.data()[2], U256::from(2)); + assert_eq!(interpreter.stack.data()[1], U256::from(0)); + assert_eq!(interpreter.stack.data()[0], U256::from(1)); + } + + #[test] + fn test_swapn_invalid_immediate() { + let mut interpreter = run_bytecode(&[SWAPN, JUMPDEST]); + assert!(interpreter.bytecode.instruction_result().is_none()); + } + + #[test] + fn test_jump_over_invalid_dupn() { + let interpreter = run_bytecode(&[PUSH1, 0x04, JUMP, DUPN, JUMPDEST]); + assert!(interpreter.bytecode.is_not_end()); + } + + #[test] + fn test_exchange_with_iszero() { + let interpreter = run_bytecode(&[ + PUSH1, 0x00, PUSH1, 0x00, PUSH1, 0x00, EXCHANGE, 0x01, ISZERO, + ]); + assert_eq!(interpreter.stack.len(), 3); + assert_eq!(interpreter.stack.data()[2], U256::from(1)); + assert_eq!(interpreter.stack.data()[1], U256::ZERO); + assert_eq!(interpreter.stack.data()[0], U256::ZERO); + } +} diff --git a/crates/interpreter/src/interpreter_types.rs b/crates/interpreter/src/interpreter_types.rs index 0195ee93fa..60f2631564 100644 --- a/crates/interpreter/src/interpreter_types.rs +++ b/crates/interpreter/src/interpreter_types.rs @@ -227,6 +227,7 @@ pub trait StackTr { /// Exchanges two values on the stack. /// /// Indexes are based from the top of the stack. + /// `n` is the first index, and the second index is calculated as `n + m`. /// /// Returns `true` if swap was successful, `false` if stack underflow. #[must_use]