diff --git a/bins/revme/src/statetest/runner.rs b/bins/revme/src/statetest/runner.rs index a38ccf0dc1..62edd643d7 100644 --- a/bins/revme/src/statetest/runner.rs +++ b/bins/revme/src/statetest/runner.rs @@ -36,6 +36,11 @@ pub enum TestErrorKind { StateRootMismatch { got: B256, expected: B256 }, #[error("Unknown private key: {0:?}")] UnknownPrivateKey(B256), + #[error("Unexpected exception: {got_exception:?} but test expects:{expected_exception:?}")] + UnexpectedException { + expected_exception: Option, + got_exception: Option, + }, #[error(transparent)] SerdeDeserialize(#[from] serde_json::Error), } @@ -94,12 +99,6 @@ fn skip_test(path: &Path) -> bool { | "loopMul.json" | "CALLBlake2f_MaxRounds.json" | "shiftCombinations.json" - - // TODO: These EIP-4844 all have exception specified. - | "emptyBlobhashList.json" // '>=Cancun': TR_EMPTYBLOB - | "wrongBlobhashVersion.json" // '>=Cancun': TR_BLOBVERSION_INVALID - | "createBlobhashTx.json" // '>=Cancun': TR_BLOBCREATE - | "blobhashListBounds7.json" // ">=Cancun": "TR_BLOBLIST_OVERSIZE" ) || path_str.contains("stEOF") } @@ -215,6 +214,7 @@ pub fn execute_test_suite( for (id, test) in tests.into_iter().enumerate() { env.tx.gas_limit = unit.transaction.gas_limit[test.indexes.gas].saturating_to(); + env.tx.data = unit .transaction .data @@ -272,11 +272,34 @@ pub fn execute_test_suite( // validate results // this is in a closure so we can have a common printing routine for errors let check = || { - let logs = match &exec_result { - Ok(ExecutionResult::Success { logs, .. }) => logs.clone(), - _ => Vec::new(), - }; - let logs_root = log_rlp_hash(&logs); + // if we expect exception revm should return error from execution. + // So we do not check logs and state root. + // + // Note that some tests that have exception and run tests from before state clear + // would touch the caller account and make it appear in state root calculation. + // This is not something that we would expect as invalid tx should not touch state. + // but as this is a cleanup of invalid tx it is not properly defined and in the end + // it does not matter. + // Test where this happens: `tests/GeneralStateTests/stTransactionTest/NoSrcAccountCreate.json` + // and you can check that we have only two "hash" values for before and after state clear. + match (&test.expect_exception, &exec_result) { + // do nothing + (None, Ok(_)) => (), + // return okay, exception is expected. + (Some(_), Err(_)) => return Ok(()), + _ => { + return Err(TestError { + name: name.clone(), + kind: TestErrorKind::UnexpectedException { + expected_exception: test.expect_exception.clone(), + got_exception: exec_result.clone().err().map(|e| e.to_string()), + }, + }); + } + } + + let logs_root = + log_rlp_hash(exec_result.as_ref().map(|r| r.logs()).unwrap_or_default()); if logs_root != test.logs { return Err(TestError { @@ -325,7 +348,7 @@ pub fn execute_test_suite( evm.database(&mut state); let path = path.display(); - println!("Test {name:?} (id: {id}, path: {path}) failed:\n{e}"); + println!("Test {name:?} (index: {index}, path: {path}) failed:\n{e}"); println!("\nTraces:"); let _ = evm.inspect_commit(TracerEip3155::new(Box::new(stdout()), false, false)); diff --git a/crates/interpreter/src/instruction_result.rs b/crates/interpreter/src/instruction_result.rs index baf371b465..6644a58e81 100644 --- a/crates/interpreter/src/instruction_result.rs +++ b/crates/interpreter/src/instruction_result.rs @@ -17,10 +17,10 @@ pub enum InstructionResult { // error codes OutOfGas = 0x50, - MemoryOOG = 0x51, - MemoryLimitOOG = 0x52, - PrecompileOOG = 0x53, - InvalidOperandOOG = 0x54, + MemoryOOG, + MemoryLimitOOG, + PrecompileOOG, + InvalidOperandOOG, OpcodeNotFound, CallNotAllowedInsideStatic, StateChangeDuringStaticCall, diff --git a/crates/interpreter/src/instructions.rs b/crates/interpreter/src/instructions.rs index b58d51db0a..5088aa4dd8 100644 --- a/crates/interpreter/src/instructions.rs +++ b/crates/interpreter/src/instructions.rs @@ -1,185 +1,15 @@ #[macro_use] -mod macros; - -mod arithmetic; -mod bitwise; -mod control; -mod host; -mod host_env; -mod i256; -mod memory; +pub mod macros; + +pub mod arithmetic; +pub mod bitwise; +pub mod control; +pub mod host; +pub mod host_env; +pub mod i256; +pub mod memory; pub mod opcode; -mod stack; -mod system; - -use crate::{interpreter::Interpreter, primitives::Spec, Host}; -pub use opcode::{OpCode, OPCODE_JUMPMAP}; - -pub use crate::{return_ok, return_revert, InstructionResult}; -pub fn return_stop(interpreter: &mut Interpreter, _host: &mut dyn Host) { - interpreter.instruction_result = InstructionResult::Stop; -} -pub fn return_invalid(interpreter: &mut Interpreter, _host: &mut dyn Host) { - interpreter.instruction_result = InstructionResult::InvalidFEOpcode; -} - -pub fn return_not_found(interpreter: &mut Interpreter, _host: &mut dyn Host) { - interpreter.instruction_result = InstructionResult::OpcodeNotFound; -} - -#[inline(always)] -pub fn eval(opcode: u8, interp: &mut Interpreter, host: &mut H) { - match opcode { - opcode::STOP => return_stop(interp, host), - opcode::ADD => arithmetic::wrapped_add(interp, host), - opcode::MUL => arithmetic::wrapping_mul(interp, host), - opcode::SUB => arithmetic::wrapping_sub(interp, host), - opcode::DIV => arithmetic::div(interp, host), - opcode::SDIV => arithmetic::sdiv(interp, host), - opcode::MOD => arithmetic::rem(interp, host), - opcode::SMOD => arithmetic::smod(interp, host), - opcode::ADDMOD => arithmetic::addmod(interp, host), - opcode::MULMOD => arithmetic::mulmod(interp, host), - opcode::EXP => arithmetic::eval_exp::(interp, host), - opcode::SIGNEXTEND => arithmetic::signextend(interp, host), - opcode::LT => bitwise::lt(interp, host), - opcode::GT => bitwise::gt(interp, host), - opcode::SLT => bitwise::slt(interp, host), - opcode::SGT => bitwise::sgt(interp, host), - opcode::EQ => bitwise::eq(interp, host), - opcode::ISZERO => bitwise::iszero(interp, host), - opcode::AND => bitwise::bitand(interp, host), - opcode::OR => bitwise::bitor(interp, host), - opcode::XOR => bitwise::bitxor(interp, host), - opcode::NOT => bitwise::not(interp, host), - opcode::BYTE => bitwise::byte(interp, host), - opcode::SHL => bitwise::shl::(interp, host), - opcode::SHR => bitwise::shr::(interp, host), - opcode::SAR => bitwise::sar::(interp, host), - opcode::KECCAK256 => system::calculate_keccak256(interp, host), - opcode::ADDRESS => system::address(interp, host), - opcode::BALANCE => host::balance::(interp, host), - opcode::SELFBALANCE => host::selfbalance::(interp, host), - opcode::BLOBHASH => host_env::blob_hash::(interp, host), - opcode::CODESIZE => system::codesize(interp, host), - opcode::CODECOPY => system::codecopy(interp, host), - opcode::CALLDATALOAD => system::calldataload(interp, host), - opcode::CALLDATASIZE => system::calldatasize(interp, host), - opcode::CALLDATACOPY => system::calldatacopy(interp, host), - opcode::POP => stack::pop(interp, host), - opcode::MLOAD => memory::mload(interp, host), - opcode::MSTORE => memory::mstore(interp, host), - opcode::MSTORE8 => memory::mstore8(interp, host), - opcode::JUMP => control::jump(interp, host), - opcode::JUMPI => control::jumpi(interp, host), - opcode::PC => control::pc(interp, host), - opcode::MSIZE => memory::msize(interp, host), - opcode::JUMPDEST => control::jumpdest(interp, host), - opcode::PUSH0 => stack::push0::(interp, host), - opcode::PUSH1 => stack::push::<1>(interp, host), - opcode::PUSH2 => stack::push::<2>(interp, host), - opcode::PUSH3 => stack::push::<3>(interp, host), - opcode::PUSH4 => stack::push::<4>(interp, host), - opcode::PUSH5 => stack::push::<5>(interp, host), - opcode::PUSH6 => stack::push::<6>(interp, host), - opcode::PUSH7 => stack::push::<7>(interp, host), - opcode::PUSH8 => stack::push::<8>(interp, host), - opcode::PUSH9 => stack::push::<9>(interp, host), - opcode::PUSH10 => stack::push::<10>(interp, host), - opcode::PUSH11 => stack::push::<11>(interp, host), - opcode::PUSH12 => stack::push::<12>(interp, host), - opcode::PUSH13 => stack::push::<13>(interp, host), - opcode::PUSH14 => stack::push::<14>(interp, host), - opcode::PUSH15 => stack::push::<15>(interp, host), - opcode::PUSH16 => stack::push::<16>(interp, host), - opcode::PUSH17 => stack::push::<17>(interp, host), - opcode::PUSH18 => stack::push::<18>(interp, host), - opcode::PUSH19 => stack::push::<19>(interp, host), - opcode::PUSH20 => stack::push::<20>(interp, host), - opcode::PUSH21 => stack::push::<21>(interp, host), - opcode::PUSH22 => stack::push::<22>(interp, host), - opcode::PUSH23 => stack::push::<23>(interp, host), - opcode::PUSH24 => stack::push::<24>(interp, host), - opcode::PUSH25 => stack::push::<25>(interp, host), - opcode::PUSH26 => stack::push::<26>(interp, host), - opcode::PUSH27 => stack::push::<27>(interp, host), - opcode::PUSH28 => stack::push::<28>(interp, host), - opcode::PUSH29 => stack::push::<29>(interp, host), - opcode::PUSH30 => stack::push::<30>(interp, host), - opcode::PUSH31 => stack::push::<31>(interp, host), - opcode::PUSH32 => stack::push::<32>(interp, host), - opcode::DUP1 => stack::dup::<1>(interp, host), - opcode::DUP2 => stack::dup::<2>(interp, host), - opcode::DUP3 => stack::dup::<3>(interp, host), - opcode::DUP4 => stack::dup::<4>(interp, host), - opcode::DUP5 => stack::dup::<5>(interp, host), - opcode::DUP6 => stack::dup::<6>(interp, host), - opcode::DUP7 => stack::dup::<7>(interp, host), - opcode::DUP8 => stack::dup::<8>(interp, host), - opcode::DUP9 => stack::dup::<9>(interp, host), - opcode::DUP10 => stack::dup::<10>(interp, host), - opcode::DUP11 => stack::dup::<11>(interp, host), - opcode::DUP12 => stack::dup::<12>(interp, host), - opcode::DUP13 => stack::dup::<13>(interp, host), - opcode::DUP14 => stack::dup::<14>(interp, host), - opcode::DUP15 => stack::dup::<15>(interp, host), - opcode::DUP16 => stack::dup::<16>(interp, host), - - opcode::SWAP1 => stack::swap::<1>(interp, host), - opcode::SWAP2 => stack::swap::<2>(interp, host), - opcode::SWAP3 => stack::swap::<3>(interp, host), - opcode::SWAP4 => stack::swap::<4>(interp, host), - opcode::SWAP5 => stack::swap::<5>(interp, host), - opcode::SWAP6 => stack::swap::<6>(interp, host), - opcode::SWAP7 => stack::swap::<7>(interp, host), - opcode::SWAP8 => stack::swap::<8>(interp, host), - opcode::SWAP9 => stack::swap::<9>(interp, host), - opcode::SWAP10 => stack::swap::<10>(interp, host), - opcode::SWAP11 => stack::swap::<11>(interp, host), - opcode::SWAP12 => stack::swap::<12>(interp, host), - opcode::SWAP13 => stack::swap::<13>(interp, host), - opcode::SWAP14 => stack::swap::<14>(interp, host), - opcode::SWAP15 => stack::swap::<15>(interp, host), - opcode::SWAP16 => stack::swap::<16>(interp, host), +pub mod stack; +pub mod system; - opcode::RETURN => control::ret(interp, host), - opcode::REVERT => control::revert::(interp, host), - opcode::INVALID => return_invalid(interp, host), - opcode::BASEFEE => host_env::basefee::(interp, host), - opcode::ORIGIN => host_env::origin(interp, host), - opcode::CALLER => system::caller(interp, host), - opcode::CALLVALUE => system::callvalue(interp, host), - opcode::GASPRICE => host_env::gasprice(interp, host), - opcode::EXTCODESIZE => host::extcodesize::(interp, host), - opcode::EXTCODEHASH => host::extcodehash::(interp, host), - opcode::EXTCODECOPY => host::extcodecopy::(interp, host), - opcode::RETURNDATASIZE => system::returndatasize::(interp, host), - opcode::RETURNDATACOPY => system::returndatacopy::(interp, host), - opcode::BLOCKHASH => host::blockhash(interp, host), - opcode::COINBASE => host_env::coinbase(interp, host), - opcode::TIMESTAMP => host_env::timestamp(interp, host), - opcode::NUMBER => host_env::number(interp, host), - opcode::DIFFICULTY => host_env::difficulty::(interp, host), - opcode::GASLIMIT => host_env::gaslimit(interp, host), - opcode::SLOAD => host::sload::(interp, host), - opcode::SSTORE => host::sstore::(interp, host), - opcode::TSTORE => host::tstore::(interp, host), - opcode::TLOAD => host::tload::(interp, host), - opcode::GAS => system::gas(interp, host), - opcode::LOG0 => host::log::<0>(interp, host), - opcode::LOG1 => host::log::<1>(interp, host), - opcode::LOG2 => host::log::<2>(interp, host), - opcode::LOG3 => host::log::<3>(interp, host), - opcode::LOG4 => host::log::<4>(interp, host), - opcode::SELFDESTRUCT => host::selfdestruct::(interp, host), - opcode::CREATE => host::create::(interp, host), //check - opcode::CREATE2 => host::create::(interp, host), //check - opcode::CALL => host::call::(interp, host), //check - opcode::CALLCODE => host::call_code::(interp, host), //check - opcode::DELEGATECALL => host::delegate_call::(interp, host), //check - opcode::STATICCALL => host::static_call::(interp, host), //check - opcode::CHAINID => host_env::chainid::(interp, host), - opcode::MCOPY => memory::mcopy::(interp, host), - _ => return_not_found(interp, host), - } -} +pub use opcode::{Instruction, OpCode, OPCODE_JUMPMAP}; diff --git a/crates/interpreter/src/instructions/arithmetic.rs b/crates/interpreter/src/instructions/arithmetic.rs index 188716c5e3..b58fa7ce1c 100644 --- a/crates/interpreter/src/instructions/arithmetic.rs +++ b/crates/interpreter/src/instructions/arithmetic.rs @@ -26,7 +26,9 @@ pub fn wrapping_sub(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn div(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::LOW); pop_top!(interpreter, op1, op2); - *op2 = op1.checked_div(*op2).unwrap_or_default() + if *op2 != U256::ZERO { + *op2 = op1.wrapping_div(*op2); + } } pub fn sdiv(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -38,7 +40,9 @@ pub fn sdiv(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn rem(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::LOW); pop_top!(interpreter, op1, op2); - *op2 = op1.checked_rem(*op2).unwrap_or_default() + if *op2 != U256::ZERO { + *op2 = op1.wrapping_rem(*op2); + } } pub fn smod(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -46,7 +50,7 @@ pub fn smod(interpreter: &mut Interpreter, _host: &mut dyn Host) { pop_top!(interpreter, op1, op2); if *op2 != U256::ZERO { *op2 = i256_mod(op1, *op2) - }; + } } pub fn addmod(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -61,7 +65,7 @@ pub fn mulmod(interpreter: &mut Interpreter, _host: &mut dyn Host) { *op3 = op1.mul_mod(op2, *op3) } -pub fn eval_exp(interpreter: &mut Interpreter, _host: &mut dyn Host) { +pub fn exp(interpreter: &mut Interpreter, _host: &mut dyn Host) { pop_top!(interpreter, op1, op2); gas_or_fail!(interpreter, gas::exp_cost::(*op2)); *op2 = op1.pow(*op2); diff --git a/crates/interpreter/src/instructions/bitwise.rs b/crates/interpreter/src/instructions/bitwise.rs index f46dc0aa9a..3effdee086 100644 --- a/crates/interpreter/src/instructions/bitwise.rs +++ b/crates/interpreter/src/instructions/bitwise.rs @@ -1,31 +1,21 @@ use super::i256::{i256_cmp, i256_sign_compl, two_compl, Sign}; use crate::{ gas, - primitives::SpecId::CONSTANTINOPLE, primitives::{Spec, U256}, Host, InstructionResult, Interpreter, }; use core::cmp::Ordering; -use core::ops::{BitAnd, BitOr, BitXor}; pub fn lt(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - *op2 = if op1.lt(op2) { - U256::from(1) - } else { - U256::ZERO - }; + *op2 = U256::from(op1 < *op2); } pub fn gt(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - *op2 = if op1.gt(op2) { - U256::from(1) - } else { - U256::ZERO - }; + *op2 = U256::from(op1 > *op2); } pub fn slt(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -43,36 +33,31 @@ pub fn sgt(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn eq(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - *op2 = if op1.eq(op2) { - U256::from(1) - } else { - U256::ZERO - }; + *op2 = U256::from(op1 == *op2); } pub fn iszero(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1); - *op1 = if *op1 == U256::ZERO { - U256::from(1) - } else { - U256::ZERO - }; + *op1 = U256::from(*op1 == U256::ZERO); } + pub fn bitand(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - *op2 = op1.bitand(*op2); + *op2 = op1 & *op2; } + pub fn bitor(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - *op2 = op1.bitor(*op2); + *op2 = op1 | *op2; } + pub fn bitxor(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - *op2 = op1.bitxor(*op2); + *op2 = op1 ^ *op2; } pub fn not(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -84,36 +69,35 @@ pub fn not(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn byte(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); - let mut ret = U256::ZERO; let o1 = as_usize_saturated!(op1); - if o1 < 32 { - let o2 = &*op2; - ret = (o2 << (8 * o1)) >> (8 * 31); - } - - *op2 = ret; + *op2 = if o1 < 32 { + // `31 - o1` because `byte` returns LE, while we want BE + U256::from(op2.byte(31 - o1)) + } else { + U256::ZERO + }; } +/// EIP-145: Bitwise shifting instructions in EVM pub fn shl(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // EIP-145: Bitwise shifting instructions in EVM - check!(interpreter, SPEC::enabled(CONSTANTINOPLE)); + check!(interpreter, CONSTANTINOPLE); gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); *op2 <<= as_usize_saturated!(op1); } +/// EIP-145: Bitwise shifting instructions in EVM pub fn shr(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // EIP-145: Bitwise shifting instructions in EVM - check!(interpreter, SPEC::enabled(CONSTANTINOPLE)); + check!(interpreter, CONSTANTINOPLE); gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); *op2 >>= as_usize_saturated!(op1); } +/// EIP-145: Bitwise shifting instructions in EVM pub fn sar(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // EIP-145: Bitwise shifting instructions in EVM - check!(interpreter, SPEC::enabled(CONSTANTINOPLE)); + check!(interpreter, CONSTANTINOPLE); gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, op1, op2); diff --git a/crates/interpreter/src/instructions/control.rs b/crates/interpreter/src/instructions/control.rs index ccb532f6c5..d265240fee 100644 --- a/crates/interpreter/src/instructions/control.rs +++ b/crates/interpreter/src/instructions/control.rs @@ -1,6 +1,7 @@ use crate::{ - gas, interpreter::Interpreter, primitives::Spec, primitives::SpecId::*, primitives::U256, Host, - InstructionResult, + gas, + primitives::{Spec, U256}, + Host, InstructionResult, Interpreter, }; pub fn jump(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -39,35 +40,44 @@ pub fn jumpdest(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn pc(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::BASE); + // - 1 because we have already advanced the instruction pointer in `Interpreter::step` push!(interpreter, U256::from(interpreter.program_counter() - 1)); } -pub fn ret(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // zero gas cost gas!(interp,gas::ZERO); - pop!(interpreter, start, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); - if len == 0 { - interpreter.return_range = usize::MAX..usize::MAX; - } else { - let offset = as_usize_or_fail!(interpreter, start, InstructionResult::InvalidOperandOOG); +#[inline(always)] +fn return_inner(interpreter: &mut Interpreter, result: InstructionResult) { + // zero gas cost + // gas!(interpreter, gas::ZERO); + pop!(interpreter, offset, len); + let len = as_usize_or_fail!(interpreter, len); + // important: offset must be ignored if len is zero + if len != 0 { + let offset = as_usize_or_fail!(interpreter, offset); memory_resize!(interpreter, offset, len); - interpreter.return_range = offset..(offset + len); + interpreter.return_offset = offset; } - interpreter.instruction_result = InstructionResult::Return; + interpreter.return_len = len; + interpreter.instruction_result = result; } +pub fn ret(interpreter: &mut Interpreter, _host: &mut dyn Host) { + return_inner(interpreter, InstructionResult::Return) +} + +/// EIP-140: REVERT instruction pub fn revert(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // zero gas cost gas!(interp,gas::ZERO); - // EIP-140: REVERT instruction - check!(interpreter, SPEC::enabled(BYZANTIUM)); - pop!(interpreter, start, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); - if len == 0 { - interpreter.return_range = usize::MAX..usize::MAX; - } else { - let offset = as_usize_or_fail!(interpreter, start, InstructionResult::InvalidOperandOOG); - memory_resize!(interpreter, offset, len); - interpreter.return_range = offset..(offset + len); - } - interpreter.instruction_result = InstructionResult::Revert; + check!(interpreter, BYZANTIUM); + return_inner(interpreter, InstructionResult::Revert) +} + +pub fn stop(interpreter: &mut Interpreter, _host: &mut dyn Host) { + interpreter.instruction_result = InstructionResult::Stop; +} + +pub fn invalid(interpreter: &mut Interpreter, _host: &mut dyn Host) { + interpreter.instruction_result = InstructionResult::InvalidFEOpcode; +} + +pub fn not_found(interpreter: &mut Interpreter, _host: &mut dyn Host) { + interpreter.instruction_result = InstructionResult::OpcodeNotFound; } diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index faa89ca5e4..927448392b 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -3,11 +3,11 @@ use crate::MAX_INITCODE_SIZE; use crate::{ gas::{self, COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST}, interpreter::Interpreter, + primitives::{Bytes, Spec, SpecId::*, B160, B256, U256}, return_ok, return_revert, CallContext, CallInputs, CallScheme, CreateInputs, CreateScheme, - Host, InstructionResult, Transfer, + Host, InstructionResult, Transfer, MAX_INITCODE_SIZE, }; -use alloc::boxed::Box; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; use core::cmp::min; use revm_primitives::BLOCK_HASH_HISTORY; @@ -31,9 +31,9 @@ pub fn balance(interpreter: &mut Interpreter, host: &mut dyn Host) { push!(interpreter, balance); } +/// EIP-1884: Repricing for trie-size-dependent opcodes pub fn selfbalance(interpreter: &mut Interpreter, host: &mut dyn Host) { - // EIP-1884: Repricing for trie-size-dependent opcodes - check!(interpreter, SPEC::enabled(ISTANBUL)); + check!(interpreter, ISTANBUL); gas!(interpreter, gas::LOW); let Some((balance, _)) = host.balance(interpreter.contract.address) else { interpreter.instruction_result = InstructionResult::FatalExternalError; @@ -66,8 +66,9 @@ pub fn extcodesize(interpreter: &mut Interpreter, host: &mut dyn Hos push!(interpreter, U256::from(code.len())); } +/// EIP-1052: EXTCODEHASH opcode pub fn extcodehash(interpreter: &mut Interpreter, host: &mut dyn Host) { - check!(interpreter, SPEC::enabled(CONSTANTINOPLE)); // EIP-1052: EXTCODEHASH opcode + check!(interpreter, CONSTANTINOPLE); pop_address!(interpreter, address); let Some((code_hash, is_cold)) = host.code_hash(address) else { interpreter.instruction_result = InstructionResult::FatalExternalError; @@ -99,7 +100,7 @@ pub fn extcodecopy(interpreter: &mut Interpreter, host: &mut dyn Hos return; }; - let len = as_usize_or_fail!(interpreter, len_u256, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len_u256); gas_or_fail!( interpreter, gas::extcodecopy_cost::(len as u64, is_cold) @@ -107,11 +108,7 @@ pub fn extcodecopy(interpreter: &mut Interpreter, host: &mut dyn Hos if len == 0 { return; } - let memory_offset = as_usize_or_fail!( - interpreter, - memory_offset, - InstructionResult::InvalidOperandOOG - ); + let memory_offset = as_usize_or_fail!(interpreter, memory_offset); let code_offset = min(as_usize_saturated!(code_offset), code.len()); memory_resize!(interpreter, memory_offset, len); @@ -168,10 +165,10 @@ pub fn sstore(interpreter: &mut Interpreter, host: &mut dyn Host) { refund!(interpreter, gas::sstore_refund::(original, old, new)); } +/// EIP-1153: Transient storage opcodes /// Store value to transient storage -pub fn tstore(interpreter: &mut Interpreter, host: &mut H) { - // EIP-1153: Transient storage opcodes - check!(interpreter, SPEC::enabled(CANCUN)); +pub fn tstore(interpreter: &mut Interpreter, host: &mut dyn Host) { + check!(interpreter, CANCUN); check_staticcall!(interpreter); gas!(interpreter, gas::WARM_STORAGE_READ_COST); @@ -180,10 +177,10 @@ pub fn tstore(interpreter: &mut Interpreter, host: &mut H) host.tstore(interpreter.contract.address, index, value); } +/// EIP-1153: Transient storage opcodes /// Load value from transient storage -pub fn tload(interpreter: &mut Interpreter, host: &mut H) { - // EIP-1153: Transient storage opcodes - check!(interpreter, SPEC::enabled(CANCUN)); +pub fn tload(interpreter: &mut Interpreter, host: &mut dyn Host) { + check!(interpreter, CANCUN); gas!(interpreter, gas::WARM_STORAGE_READ_COST); pop_top!(interpreter, index); @@ -191,27 +188,27 @@ pub fn tload(interpreter: &mut Interpreter, host: &mut H) { *index = host.tload(interpreter.contract.address, *index); } -pub fn log(interpreter: &mut Interpreter, host: &mut dyn Host) { +pub fn log(interpreter: &mut Interpreter, host: &mut dyn Host) { check_staticcall!(interpreter); pop!(interpreter, offset, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); - gas_or_fail!(interpreter, gas::log_cost(N, len as u64)); + let len = as_usize_or_fail!(interpreter, len); + gas_or_fail!(interpreter, gas::log_cost(N as u8, len as u64)); let data = if len == 0 { Bytes::new() } else { - let offset = as_usize_or_fail!(interpreter, offset, InstructionResult::InvalidOperandOOG); + let offset = as_usize_or_fail!(interpreter, offset); memory_resize!(interpreter, offset, len); - Bytes::copy_from_slice(interpreter.memory.get_slice(offset, len)) + Bytes::copy_from_slice(interpreter.memory.slice(offset, len)) }; - let n = N as usize; - if interpreter.stack.len() < n { + + if interpreter.stack.len() < N { interpreter.instruction_result = InstructionResult::StackUnderflow; return; } - let mut topics = Vec::with_capacity(n); - for _ in 0..(n) { + let mut topics = Vec::with_capacity(N); + for _ in 0..N { // Safety: stack bounds already checked few lines above topics.push(B256::new(unsafe { interpreter.stack.pop_unsafe().to_be_bytes() @@ -246,24 +243,20 @@ pub fn prepare_create_inputs( create_inputs: &mut Option>, ) { check_staticcall!(interpreter); + + // EIP-1014: Skinny CREATE2 if IS_CREATE2 { - // EIP-1014: Skinny CREATE2 - check!(interpreter, SPEC::enabled(PETERSBURG)); + check!(interpreter, PETERSBURG); } interpreter.return_data_buffer = Bytes::new(); pop!(interpreter, value, code_offset, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len); let code = if len == 0 { Bytes::new() } else { - let code_offset = as_usize_or_fail!( - interpreter, - code_offset, - InstructionResult::InvalidOperandOOG - ); // EIP-3860: Limit and meter initcode if SPEC::enabled(SHANGHAI) { // Limit is set as double of max contract bytecode size @@ -279,8 +272,10 @@ pub fn prepare_create_inputs( } gas!(interpreter, gas::initcode_cost(len as u64)); } + + let code_offset = as_usize_or_fail!(interpreter, code_offset); memory_resize!(interpreter, code_offset, len); - Bytes::copy_from_slice(interpreter.memory.get_slice(code_offset, len)) + Bytes::copy_from_slice(interpreter.memory.slice(code_offset, len)) }; let scheme = if IS_CREATE2 { @@ -333,6 +328,7 @@ pub fn create( match return_reason { return_ok!() => { push_b256!(interpreter, address.unwrap_or_default().into_word()); + if crate::USE_GAS { interpreter.gas.erase_cost(gas.remaining()); interpreter.gas.record_refund(gas.refunded()); @@ -340,6 +336,7 @@ pub fn create( } return_revert!() => { push_b256!(interpreter, B256::ZERO); + if crate::USE_GAS { interpreter.gas.erase_cost(gas.remaining()); } @@ -354,19 +351,19 @@ pub fn create( } pub fn call(interpreter: &mut Interpreter, host: &mut dyn Host) { - call_inner::(interpreter, CallScheme::Call, host); + call_inner::(CallScheme::Call, interpreter, host); } pub fn call_code(interpreter: &mut Interpreter, host: &mut dyn Host) { - call_inner::(interpreter, CallScheme::CallCode, host); + call_inner::(CallScheme::CallCode, interpreter, host); } pub fn delegate_call(interpreter: &mut Interpreter, host: &mut dyn Host) { - call_inner::(interpreter, CallScheme::DelegateCall, host); + call_inner::(CallScheme::DelegateCall, interpreter, host); } pub fn static_call(interpreter: &mut Interpreter, host: &mut dyn Host) { - call_inner::(interpreter, CallScheme::StaticCall, host); + call_inner::(CallScheme::StaticCall, interpreter, host); } #[inline(never)] @@ -400,23 +397,18 @@ fn prepare_call_inputs( pop!(interpreter, in_offset, in_len, out_offset, out_len); - let in_len = as_usize_or_fail!(interpreter, in_len, InstructionResult::InvalidOperandOOG); + let in_len = as_usize_or_fail!(interpreter, in_len); let input = if in_len != 0 { - let in_offset = - as_usize_or_fail!(interpreter, in_offset, InstructionResult::InvalidOperandOOG); + let in_offset = as_usize_or_fail!(interpreter, in_offset); memory_resize!(interpreter, in_offset, in_len); - Bytes::copy_from_slice(interpreter.memory.get_slice(in_offset, in_len)) + Bytes::copy_from_slice(interpreter.memory.slice(in_offset, in_len)) } else { Bytes::new() }; - *result_len = as_usize_or_fail!(interpreter, out_len, InstructionResult::InvalidOperandOOG); + *result_len = as_usize_or_fail!(interpreter, out_len); *result_offset = if *result_len != 0 { - let out_offset = as_usize_or_fail!( - interpreter, - out_offset, - InstructionResult::InvalidOperandOOG - ); + let out_offset = as_usize_or_fail!(interpreter, out_offset); memory_resize!(interpreter, out_offset, *result_len); out_offset } else { @@ -486,10 +478,10 @@ fn prepare_call_inputs( ) ); - // take l64 part of gas_limit + // EIP-150: Gas cost changes for IO-heavy operations let mut gas_limit = if SPEC::enabled(TANGERINE) { - //EIP-150: Gas cost changes for IO-heavy operations let gas = interpreter.gas().remaining(); + // take l64 part of gas_limit min(gas - gas / 64, local_gas_limit) } else { local_gas_limit @@ -514,13 +506,15 @@ fn prepare_call_inputs( } pub fn call_inner( - interpreter: &mut Interpreter, scheme: CallScheme, + interpreter: &mut Interpreter, host: &mut dyn Host, ) { match scheme { - CallScheme::DelegateCall => check!(interpreter, SPEC::enabled(HOMESTEAD)), // EIP-7: DELEGATECALL - CallScheme::StaticCall => check!(interpreter, SPEC::enabled(BYZANTIUM)), // EIP-214: New opcode STATICCALL + // EIP-7: DELEGATECALL + CallScheme::DelegateCall => check!(interpreter, HOMESTEAD), + // EIP-214: New opcode STATICCALL + CallScheme::StaticCall => check!(interpreter, BYZANTIUM), _ => (), } interpreter.return_data_buffer = Bytes::new(); diff --git a/crates/interpreter/src/instructions/host_env.rs b/crates/interpreter/src/instructions/host_env.rs index a539a1d216..803fb550bb 100644 --- a/crates/interpreter/src/instructions/host_env.rs +++ b/crates/interpreter/src/instructions/host_env.rs @@ -1,13 +1,12 @@ use crate::{ gas, - interpreter::Interpreter, primitives::{Spec, SpecId::*, U256}, - Host, InstructionResult, + Host, InstructionResult, Interpreter, }; +/// EIP-1344: ChainID opcode pub fn chainid(interpreter: &mut Interpreter, host: &mut dyn Host) { - // EIP-1344: ChainID opcode - check!(interpreter, SPEC::enabled(ISTANBUL)); + check!(interpreter, ISTANBUL); gas!(interpreter, gas::BASE); push!(interpreter, U256::from(host.env().cfg.chain_id)); } @@ -27,7 +26,7 @@ pub fn number(interpreter: &mut Interpreter, host: &mut dyn Host) { push!(interpreter, host.env().block.number); } -pub fn difficulty(interpreter: &mut Interpreter, host: &mut H) { +pub fn difficulty(interpreter: &mut Interpreter, host: &mut dyn Host) { gas!(interpreter, gas::BASE); if SPEC::enabled(MERGE) { push_b256!(interpreter, host.env().block.prevrandao.unwrap()); @@ -46,10 +45,10 @@ pub fn gasprice(interpreter: &mut Interpreter, host: &mut dyn Host) { push!(interpreter, host.env().effective_gas_price()); } +/// EIP-3198: BASEFEE opcode pub fn basefee(interpreter: &mut Interpreter, host: &mut dyn Host) { + check!(interpreter, LONDON); gas!(interpreter, gas::BASE); - // EIP-3198: BASEFEE opcode - check!(interpreter, SPEC::enabled(LONDON)); push!(interpreter, host.env().block.basefee); } @@ -60,7 +59,7 @@ pub fn origin(interpreter: &mut Interpreter, host: &mut dyn Host) { // EIP-4844: Shard Blob Transactions pub fn blob_hash(interpreter: &mut Interpreter, host: &mut dyn Host) { - check!(interpreter, SPEC::enabled(CANCUN)); + check!(interpreter, CANCUN); gas!(interpreter, gas::VERYLOW); pop_top!(interpreter, index); let i = as_usize_saturated!(index); diff --git a/crates/interpreter/src/instructions/i256.rs b/crates/interpreter/src/instructions/i256.rs index 138de0da23..5b7d21de88 100644 --- a/crates/interpreter/src/instructions/i256.rs +++ b/crates/interpreter/src/instructions/i256.rs @@ -3,7 +3,7 @@ use core::cmp::Ordering; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(i8)] -pub(super) enum Sign { +pub enum Sign { // same as `cmp::Ordering` Minus = -1, Zero = 0, @@ -21,7 +21,7 @@ const MIN_NEGATIVE_VALUE: U256 = U256::from_limbs([ const FLIPH_BITMASK_U64: u64 = 0x7FFFFFFFFFFFFFFF; #[inline(always)] -pub(super) fn i256_sign(val: &U256) -> Sign { +pub fn i256_sign(val: &U256) -> Sign { if val.bit(U256::BITS - 1) { Sign::Minus } else { @@ -31,7 +31,7 @@ pub(super) fn i256_sign(val: &U256) -> Sign { } #[inline(always)] -pub(super) fn i256_sign_compl(val: &mut U256) -> Sign { +pub fn i256_sign_compl(val: &mut U256) -> Sign { let sign = i256_sign(val); if sign == Sign::Minus { two_compl_mut(val); @@ -48,17 +48,17 @@ fn u256_remove_sign(val: &mut U256) { } #[inline(always)] -pub(super) fn two_compl_mut(op: &mut U256) { +pub fn two_compl_mut(op: &mut U256) { *op = two_compl(*op); } #[inline(always)] -pub(super) fn two_compl(op: U256) -> U256 { +pub fn two_compl(op: U256) -> U256 { op.wrapping_neg() } #[inline(always)] -pub(super) fn i256_cmp(first: &U256, second: &U256) -> Ordering { +pub fn i256_cmp(first: &U256, second: &U256) -> Ordering { let first_sign = i256_sign(first); let second_sign = i256_sign(second); match first_sign.cmp(&second_sign) { @@ -70,7 +70,7 @@ pub(super) fn i256_cmp(first: &U256, second: &U256) -> Ordering { } #[inline(always)] -pub(super) fn i256_div(mut first: U256, mut second: U256) -> U256 { +pub fn i256_div(mut first: U256, mut second: U256) -> U256 { let second_sign = i256_sign_compl(&mut second); if second_sign == Sign::Zero { return U256::ZERO; @@ -99,7 +99,7 @@ pub(super) fn i256_div(mut first: U256, mut second: U256) -> U256 { } #[inline(always)] -pub(super) fn i256_mod(mut first: U256, mut second: U256) -> U256 { +pub fn i256_mod(mut first: U256, mut second: U256) -> U256 { let first_sign = i256_sign_compl(&mut first); if first_sign == Sign::Zero { return U256::ZERO; diff --git a/crates/interpreter/src/instructions/macros.rs b/crates/interpreter/src/instructions/macros.rs index 3c06e02ca1..714a83368b 100644 --- a/crates/interpreter/src/instructions/macros.rs +++ b/crates/interpreter/src/instructions/macros.rs @@ -8,8 +8,9 @@ macro_rules! check_staticcall { } macro_rules! check { - ($interp:expr, $expresion:expr) => { - if !$expresion { + ($interp:expr, $min:ident) => { + // TODO: Force const-eval on the condition with a `const {}` block once they are stable + if !::enabled($crate::primitives::SpecId::$min) { $interp.instruction_result = InstructionResult::NotActivated; return; } diff --git a/crates/interpreter/src/instructions/memory.rs b/crates/interpreter/src/instructions/memory.rs index a33040c25f..4122a435e7 100644 --- a/crates/interpreter/src/instructions/memory.rs +++ b/crates/interpreter/src/instructions/memory.rs @@ -1,31 +1,25 @@ -use core::cmp::max; - -use revm_primitives::SpecId::CANCUN; - use crate::{ gas, - interpreter::Interpreter, primitives::{Spec, U256}, - Host, InstructionResult, + Host, InstructionResult, Interpreter, }; +use core::cmp::max; pub fn mload(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop!(interpreter, index); - let index = as_usize_or_fail!(interpreter, index, InstructionResult::InvalidOperandOOG); + let index = as_usize_or_fail!(interpreter, index); memory_resize!(interpreter, index, 32); push!( interpreter, - U256::from_be_bytes::<{ U256::BYTES }>( - interpreter.memory.get_slice(index, 32).try_into().unwrap() - ) + U256::from_be_bytes::<32>(interpreter.memory.slice(index, 32).try_into().unwrap()) ); } pub fn mstore(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop!(interpreter, index, value); - let index = as_usize_or_fail!(interpreter, index, InstructionResult::InvalidOperandOOG); + let index = as_usize_or_fail!(interpreter, index); memory_resize!(interpreter, index, 32); interpreter.memory.set_u256(index, value); } @@ -33,38 +27,33 @@ pub fn mstore(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn mstore8(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); pop!(interpreter, index, value); - let index = as_usize_or_fail!(interpreter, index, InstructionResult::InvalidOperandOOG); + let index = as_usize_or_fail!(interpreter, index); memory_resize!(interpreter, index, 1); - let value = value.as_le_bytes()[0]; - // Safety: we resized our memory two lines above. - unsafe { interpreter.memory.set_byte(index, value) } + interpreter.memory.set_byte(index, value.byte(0)) } pub fn msize(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::BASE); - push!(interpreter, U256::from(interpreter.memory.effective_len())); + push!(interpreter, U256::from(interpreter.memory.len())); } -// From EIP-5656 MCOPY +// EIP-5656: MCOPY - Memory copying instruction pub fn mcopy(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // Opcode enabled in Cancun. - // EIP-5656: MCOPY - Memory copying instruction - check!(interpreter, SPEC::enabled(CANCUN)); - // get src and dest and length from stack - pop!(interpreter, dest, src, len); + check!(interpreter, CANCUN); + pop!(interpreter, dst, src, len); // into usize or fail - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len); // deduce gas gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); if len == 0 { return; } - let dest = as_usize_or_fail!(interpreter, dest, InstructionResult::InvalidOperandOOG); - let src = as_usize_or_fail!(interpreter, src, InstructionResult::InvalidOperandOOG); + let dst = as_usize_or_fail!(interpreter, dst); + let src = as_usize_or_fail!(interpreter, src); // resize memory - memory_resize!(interpreter, max(dest, src), len); + memory_resize!(interpreter, max(dst, src), len); // copy memory in place - interpreter.memory.copy(dest, src, len); + interpreter.memory.copy(dst, src, len); } diff --git a/crates/interpreter/src/instructions/opcode.rs b/crates/interpreter/src/instructions/opcode.rs index ab3df18ae3..9aaef70fec 100644 --- a/crates/interpreter/src/instructions/opcode.rs +++ b/crates/interpreter/src/instructions/opcode.rs @@ -1,171 +1,387 @@ -use crate::gas; -use crate::primitives::SpecId; +//! EVM opcode definitions and utilities. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +use super::*; +use crate::{ + gas, + primitives::{Spec, SpecId}, + Host, Interpreter, +}; +use core::fmt; + +pub type Instruction = fn(&mut Interpreter, &mut dyn Host); + +macro_rules! opcodes { + ($($val:literal => $name:ident => $f:expr),* $(,)?) => { + // Constants for each opcode. This also takes care of duplicate names. + $( + #[doc = concat!("The `", stringify!($val), "` (\"", stringify!($name),"\") opcode.")] + pub const $name: u8 = $val; + )* + + /// Maps each opcode to its name. + pub const OPCODE_JUMPMAP: [Option<&'static str>; 256] = { + let mut map = [None; 256]; + let mut prev: u8 = 0; + $( + let val: u8 = $val; + assert!(val == 0 || val > prev, "opcodes must be sorted in ascending order"); + prev = val; + map[$val] = Some(stringify!($name)); + )* + let _ = prev; + map + }; + + // Requires `inline_const` and `const_mut_refs` unstable features, + // but provides ~+2% extra performance. + // See: https://github.com/bluealloy/revm/issues/310#issuecomment-1664381513 + /* + type InstructionTable = [Instruction; 256]; + + const fn make_instruction_table() -> InstructionTable { + let mut table: InstructionTable = [control::not_found; 256]; + let mut i = 0usize; + while i < 256 { + table[i] = match i as u8 { + $($name => $f,)* + _ => control::not_found, + }; + i += 1; + } + table + } + + // in `eval`: + (const { make_instruction_table::() })[opcode as usize](interpreter, host) + */ + + /// Evaluates the opcode in the given context. + #[inline(always)] + pub(crate) fn eval(opcode: u8, interpreter: &mut Interpreter, host: &mut dyn Host) { + // See https://github.com/bluealloy/revm/issues/310#issuecomment-1664381513 + // for previous efforts on optimizing this function. + let f: Instruction = match opcode { + $($name => $f as Instruction,)* + _ => control::not_found as Instruction, + }; + f(interpreter, host); + } + }; +} + +// When adding new opcodes: +// 1. add the opcode to the list below; make sure it's sorted by opcode value +// 2. add its gas info in the `opcode_gas_info` function below +// 3. implement the opcode in the corresponding module; +// the function signature must be the exact same as the others +opcodes! { + 0x00 => STOP => control::stop, + + 0x01 => ADD => arithmetic::wrapped_add, + 0x02 => MUL => arithmetic::wrapping_mul, + 0x03 => SUB => arithmetic::wrapping_sub, + 0x04 => DIV => arithmetic::div, + 0x05 => SDIV => arithmetic::sdiv, + 0x06 => MOD => arithmetic::rem, + 0x07 => SMOD => arithmetic::smod, + 0x08 => ADDMOD => arithmetic::addmod, + 0x09 => MULMOD => arithmetic::mulmod, + 0x0A => EXP => arithmetic::exp::, + 0x0B => SIGNEXTEND => arithmetic::signextend, + // 0x0C + // 0x0D + // 0x0E + // 0x0F + 0x10 => LT => bitwise::lt, + 0x11 => GT => bitwise::gt, + 0x12 => SLT => bitwise::slt, + 0x13 => SGT => bitwise::sgt, + 0x14 => EQ => bitwise::eq, + 0x15 => ISZERO => bitwise::iszero, + 0x16 => AND => bitwise::bitand, + 0x17 => OR => bitwise::bitor, + 0x18 => XOR => bitwise::bitxor, + 0x19 => NOT => bitwise::not, + 0x1A => BYTE => bitwise::byte, + 0x1B => SHL => bitwise::shl::, + 0x1C => SHR => bitwise::shr::, + 0x1D => SAR => bitwise::sar::, + // 0x1E + // 0x1F + 0x20 => KECCAK256 => system::keccak256, + // 0x21 + // 0x22 + // 0x23 + // 0x24 + // 0x25 + // 0x26 + // 0x27 + // 0x28 + // 0x29 + // 0x2A + // 0x2B + // 0x2C + // 0x2D + // 0x2E + // 0x2F + 0x30 => ADDRESS => system::address, + 0x31 => BALANCE => host::balance::, + 0x32 => ORIGIN => host_env::origin, + 0x33 => CALLER => system::caller, + 0x34 => CALLVALUE => system::callvalue, + 0x35 => CALLDATALOAD => system::calldataload, + 0x36 => CALLDATASIZE => system::calldatasize, + 0x37 => CALLDATACOPY => system::calldatacopy, + 0x38 => CODESIZE => system::codesize, + 0x39 => CODECOPY => system::codecopy, + + 0x3A => GASPRICE => host_env::gasprice, + 0x3B => EXTCODESIZE => host::extcodesize::, + 0x3C => EXTCODECOPY => host::extcodecopy::, + 0x3D => RETURNDATASIZE => system::returndatasize::, + 0x3E => RETURNDATACOPY => system::returndatacopy::, + 0x3F => EXTCODEHASH => host::extcodehash::, + 0x40 => BLOCKHASH => host::blockhash, + 0x41 => COINBASE => host_env::coinbase, + 0x42 => TIMESTAMP => host_env::timestamp, + 0x43 => NUMBER => host_env::number, + 0x44 => DIFFICULTY => host_env::difficulty::, + 0x45 => GASLIMIT => host_env::gaslimit, + 0x46 => CHAINID => host_env::chainid::, + 0x47 => SELFBALANCE => host::selfbalance::, + 0x48 => BASEFEE => host_env::basefee::, + 0x49 => BLOBHASH => host_env::blob_hash::, + // 0x4A + // 0x4B + // 0x4C + // 0x4D + // 0x4E + // 0x4F + 0x50 => POP => stack::pop, + 0x51 => MLOAD => memory::mload, + 0x52 => MSTORE => memory::mstore, + 0x53 => MSTORE8 => memory::mstore8, + 0x54 => SLOAD => host::sload::, + 0x55 => SSTORE => host::sstore::, + 0x56 => JUMP => control::jump, + 0x57 => JUMPI => control::jumpi, + 0x58 => PC => control::pc, + 0x59 => MSIZE => memory::msize, + 0x5A => GAS => system::gas, + 0x5B => JUMPDEST => control::jumpdest, + 0x5C => TLOAD => host::tload::, + 0x5D => TSTORE => host::tstore::, + 0x5E => MCOPY => memory::mcopy::, + + 0x5F => PUSH0 => stack::push0::, + 0x60 => PUSH1 => stack::push::<1>, + 0x61 => PUSH2 => stack::push::<2>, + 0x62 => PUSH3 => stack::push::<3>, + 0x63 => PUSH4 => stack::push::<4>, + 0x64 => PUSH5 => stack::push::<5>, + 0x65 => PUSH6 => stack::push::<6>, + 0x66 => PUSH7 => stack::push::<7>, + 0x67 => PUSH8 => stack::push::<8>, + 0x68 => PUSH9 => stack::push::<9>, + 0x69 => PUSH10 => stack::push::<10>, + 0x6A => PUSH11 => stack::push::<11>, + 0x6B => PUSH12 => stack::push::<12>, + 0x6C => PUSH13 => stack::push::<13>, + 0x6D => PUSH14 => stack::push::<14>, + 0x6E => PUSH15 => stack::push::<15>, + 0x6F => PUSH16 => stack::push::<16>, + 0x70 => PUSH17 => stack::push::<17>, + 0x71 => PUSH18 => stack::push::<18>, + 0x72 => PUSH19 => stack::push::<19>, + 0x73 => PUSH20 => stack::push::<20>, + 0x74 => PUSH21 => stack::push::<21>, + 0x75 => PUSH22 => stack::push::<22>, + 0x76 => PUSH23 => stack::push::<23>, + 0x77 => PUSH24 => stack::push::<24>, + 0x78 => PUSH25 => stack::push::<25>, + 0x79 => PUSH26 => stack::push::<26>, + 0x7A => PUSH27 => stack::push::<27>, + 0x7B => PUSH28 => stack::push::<28>, + 0x7C => PUSH29 => stack::push::<29>, + 0x7D => PUSH30 => stack::push::<30>, + 0x7E => PUSH31 => stack::push::<31>, + 0x7F => PUSH32 => stack::push::<32>, + + 0x80 => DUP1 => stack::dup::<1>, + 0x81 => DUP2 => stack::dup::<2>, + 0x82 => DUP3 => stack::dup::<3>, + 0x83 => DUP4 => stack::dup::<4>, + 0x84 => DUP5 => stack::dup::<5>, + 0x85 => DUP6 => stack::dup::<6>, + 0x86 => DUP7 => stack::dup::<7>, + 0x87 => DUP8 => stack::dup::<8>, + 0x88 => DUP9 => stack::dup::<9>, + 0x89 => DUP10 => stack::dup::<10>, + 0x8A => DUP11 => stack::dup::<11>, + 0x8B => DUP12 => stack::dup::<12>, + 0x8C => DUP13 => stack::dup::<13>, + 0x8D => DUP14 => stack::dup::<14>, + 0x8E => DUP15 => stack::dup::<15>, + 0x8F => DUP16 => stack::dup::<16>, + + 0x90 => SWAP1 => stack::swap::<1>, + 0x91 => SWAP2 => stack::swap::<2>, + 0x92 => SWAP3 => stack::swap::<3>, + 0x93 => SWAP4 => stack::swap::<4>, + 0x94 => SWAP5 => stack::swap::<5>, + 0x95 => SWAP6 => stack::swap::<6>, + 0x96 => SWAP7 => stack::swap::<7>, + 0x97 => SWAP8 => stack::swap::<8>, + 0x98 => SWAP9 => stack::swap::<9>, + 0x99 => SWAP10 => stack::swap::<10>, + 0x9A => SWAP11 => stack::swap::<11>, + 0x9B => SWAP12 => stack::swap::<12>, + 0x9C => SWAP13 => stack::swap::<13>, + 0x9D => SWAP14 => stack::swap::<14>, + 0x9E => SWAP15 => stack::swap::<15>, + 0x9F => SWAP16 => stack::swap::<16>, + + 0xA0 => LOG0 => host::log::<0>, + 0xA1 => LOG1 => host::log::<1>, + 0xA2 => LOG2 => host::log::<2>, + 0xA3 => LOG3 => host::log::<3>, + 0xA4 => LOG4 => host::log::<4>, + // 0xA5 + // 0xA6 + // 0xA7 + // 0xA8 + // 0xA9 + // 0xAA + // 0xAB + // 0xAC + // 0xAD + // 0xAE + // 0xAF + // 0xB0 + // 0xB1 + // 0xB2 + // 0xB3 + // 0xB4 + // 0xB5 + // 0xB6 + // 0xB7 + // 0xB8 + // 0xB9 + // 0xBA + // 0xBB + // 0xBC + // 0xBD + // 0xBE + // 0xBF + // 0xC0 + // 0xC1 + // 0xC2 + // 0xC3 + // 0xC4 + // 0xC5 + // 0xC6 + // 0xC7 + // 0xC8 + // 0xC9 + // 0xCA + // 0xCB + // 0xCC + // 0xCD + // 0xCE + // 0xCF + // 0xD0 + // 0xD1 + // 0xD2 + // 0xD3 + // 0xD4 + // 0xD5 + // 0xD6 + // 0xD7 + // 0xD8 + // 0xD9 + // 0xDA + // 0xDB + // 0xDC + // 0xDD + // 0xDE + // 0xDF + // 0xE0 + // 0xE1 + // 0xE2 + // 0xE3 + // 0xE4 + // 0xE5 + // 0xE6 + // 0xE7 + // 0xE8 + // 0xE9 + // 0xEA + // 0xEB + // 0xEC + // 0xED + // 0xEE + // 0xEF + 0xF0 => CREATE => host::create::, + 0xF1 => CALL => host::call::, + 0xF2 => CALLCODE => host::call_code::, + 0xF3 => RETURN => control::ret, + 0xF4 => DELEGATECALL => host::delegate_call::, + 0xF5 => CREATE2 => host::create::, + // 0xF6 + // 0xF7 + // 0xF8 + // 0xF9 + 0xFA => STATICCALL => host::static_call::, + // 0xFB + // 0xF + 0xFD => REVERT => control::revert::, + 0xFE => INVALID => control::invalid, + 0xFF => SELFDESTRUCT => host::selfdestruct::, +} + +/// An EVM opcode. +/// +/// This is always a valid opcode, as declared in the [`opcode`][self] module or the +/// [`OPCODE_JUMPMAP`] constant. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] pub struct OpCode(u8); -pub const STOP: u8 = 0x00; -pub const ADD: u8 = 0x01; -pub const MUL: u8 = 0x02; -pub const SUB: u8 = 0x03; -pub const DIV: u8 = 0x04; -pub const SDIV: u8 = 0x05; -pub const MOD: u8 = 0x06; -pub const SMOD: u8 = 0x07; -pub const ADDMOD: u8 = 0x08; -pub const MULMOD: u8 = 0x09; -pub const EXP: u8 = 0x0a; -pub const SIGNEXTEND: u8 = 0x0b; - -pub const LT: u8 = 0x10; -pub const GT: u8 = 0x11; -pub const SLT: u8 = 0x12; -pub const SGT: u8 = 0x13; -pub const EQ: u8 = 0x14; -pub const ISZERO: u8 = 0x15; -pub const AND: u8 = 0x16; -pub const OR: u8 = 0x17; -pub const XOR: u8 = 0x18; -pub const NOT: u8 = 0x19; -pub const BYTE: u8 = 0x1a; - -pub const CALLDATALOAD: u8 = 0x35; -pub const CALLDATASIZE: u8 = 0x36; -pub const CALLDATACOPY: u8 = 0x37; -pub const CODESIZE: u8 = 0x38; -pub const CODECOPY: u8 = 0x39; - -pub const SHL: u8 = 0x1b; -pub const SHR: u8 = 0x1c; -pub const SAR: u8 = 0x1d; -pub const KECCAK256: u8 = 0x20; -pub const POP: u8 = 0x50; -pub const MLOAD: u8 = 0x51; -pub const MSTORE: u8 = 0x52; -pub const MSTORE8: u8 = 0x53; -pub const JUMP: u8 = 0x56; -pub const JUMPI: u8 = 0x57; -pub const PC: u8 = 0x58; -pub const MSIZE: u8 = 0x59; -pub const JUMPDEST: u8 = 0x5b; - -pub const TLOAD: u8 = 0x5c; -pub const TSTORE: u8 = 0x5d; - -pub const MCOPY: u8 = 0x5e; -pub const PUSH0: u8 = 0x5f; -pub const PUSH1: u8 = 0x60; -pub const PUSH2: u8 = 0x61; -pub const PUSH3: u8 = 0x62; -pub const PUSH4: u8 = 0x63; -pub const PUSH5: u8 = 0x64; -pub const PUSH6: u8 = 0x65; -pub const PUSH7: u8 = 0x66; -pub const PUSH8: u8 = 0x67; -pub const PUSH9: u8 = 0x68; -pub const PUSH10: u8 = 0x69; -pub const PUSH11: u8 = 0x6a; -pub const PUSH12: u8 = 0x6b; -pub const PUSH13: u8 = 0x6c; -pub const PUSH14: u8 = 0x6d; -pub const PUSH15: u8 = 0x6e; -pub const PUSH16: u8 = 0x6f; -pub const PUSH17: u8 = 0x70; -pub const PUSH18: u8 = 0x71; -pub const PUSH19: u8 = 0x72; -pub const PUSH20: u8 = 0x73; -pub const PUSH21: u8 = 0x74; -pub const PUSH22: u8 = 0x75; -pub const PUSH23: u8 = 0x76; -pub const PUSH24: u8 = 0x77; -pub const PUSH25: u8 = 0x78; -pub const PUSH26: u8 = 0x79; -pub const PUSH27: u8 = 0x7a; -pub const PUSH28: u8 = 0x7b; -pub const PUSH29: u8 = 0x7c; -pub const PUSH30: u8 = 0x7d; -pub const PUSH31: u8 = 0x7e; -pub const PUSH32: u8 = 0x7f; -pub const DUP1: u8 = 0x80; -pub const DUP2: u8 = 0x81; -pub const DUP3: u8 = 0x82; -pub const DUP4: u8 = 0x83; -pub const DUP5: u8 = 0x84; -pub const DUP6: u8 = 0x85; -pub const DUP7: u8 = 0x86; -pub const DUP8: u8 = 0x87; -pub const DUP9: u8 = 0x88; -pub const DUP10: u8 = 0x89; -pub const DUP11: u8 = 0x8a; -pub const DUP12: u8 = 0x8b; -pub const DUP13: u8 = 0x8c; -pub const DUP14: u8 = 0x8d; -pub const DUP15: u8 = 0x8e; -pub const DUP16: u8 = 0x8f; -pub const SWAP1: u8 = 0x90; -pub const SWAP2: u8 = 0x91; -pub const SWAP3: u8 = 0x92; -pub const SWAP4: u8 = 0x93; -pub const SWAP5: u8 = 0x94; -pub const SWAP6: u8 = 0x95; -pub const SWAP7: u8 = 0x96; -pub const SWAP8: u8 = 0x97; -pub const SWAP9: u8 = 0x98; -pub const SWAP10: u8 = 0x99; -pub const SWAP11: u8 = 0x9a; -pub const SWAP12: u8 = 0x9b; -pub const SWAP13: u8 = 0x9c; -pub const SWAP14: u8 = 0x9d; -pub const SWAP15: u8 = 0x9e; -pub const SWAP16: u8 = 0x9f; -pub const RETURN: u8 = 0xf3; -pub const REVERT: u8 = 0xfd; -pub const INVALID: u8 = 0xfe; -pub const ADDRESS: u8 = 0x30; -pub const BALANCE: u8 = 0x31; -pub const BASEFEE: u8 = 0x48; -pub const ORIGIN: u8 = 0x32; -pub const CALLER: u8 = 0x33; -pub const CALLVALUE: u8 = 0x34; -pub const GASPRICE: u8 = 0x3a; -pub const EXTCODESIZE: u8 = 0x3b; -pub const EXTCODECOPY: u8 = 0x3c; -pub const EXTCODEHASH: u8 = 0x3f; -pub const RETURNDATASIZE: u8 = 0x3d; -pub const RETURNDATACOPY: u8 = 0x3e; -pub const BLOCKHASH: u8 = 0x40; -pub const COINBASE: u8 = 0x41; -pub const TIMESTAMP: u8 = 0x42; -pub const NUMBER: u8 = 0x43; -pub const DIFFICULTY: u8 = 0x44; -pub const GASLIMIT: u8 = 0x45; -pub const SELFBALANCE: u8 = 0x47; -pub const BLOBHASH: u8 = 0x49; -pub const SLOAD: u8 = 0x54; -pub const SSTORE: u8 = 0x55; -pub const GAS: u8 = 0x5a; - -pub const LOG0: u8 = 0xa0; -pub const LOG1: u8 = 0xa1; -pub const LOG2: u8 = 0xa2; -pub const LOG3: u8 = 0xa3; -pub const LOG4: u8 = 0xa4; - -pub const CREATE: u8 = 0xf0; -pub const CREATE2: u8 = 0xf5; -pub const CALL: u8 = 0xf1; -pub const CALLCODE: u8 = 0xf2; -pub const DELEGATECALL: u8 = 0xf4; -pub const STATICCALL: u8 = 0xfa; -pub const SELFDESTRUCT: u8 = 0xff; -pub const CHAINID: u8 = 0x46; +impl fmt::Display for OpCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let n = self.get(); + if let Some(val) = OPCODE_JUMPMAP[n as usize] { + f.write_str(val) + } else { + write!(f, "UNKNOWN(0x{n:02X})") + } + } +} impl OpCode { - pub fn try_from_u8(opcode: u8) -> Option { - OPCODE_JUMPMAP[opcode as usize].map(|_| OpCode(opcode)) + /// Instantiate a new opcode from a u8. + #[inline] + pub const fn new(opcode: u8) -> Option { + match OPCODE_JUMPMAP[opcode as usize] { + Some(_) => Some(Self(opcode)), + None => None, + } } - pub const fn as_str(&self) -> &'static str { + /// Instantiate a new opcode from a u8 without checking if it is valid. + /// + /// # Safety + /// + /// All code using `Opcode` values assume that they are valid opcodes, so providing an invalid + /// opcode may cause undefined behavior. + #[inline] + pub unsafe fn new_unchecked(opcode: u8) -> Self { + Self(opcode) + } + + /// Returns the opcode as a string. + #[inline] + pub const fn as_str(self) -> &'static str { if let Some(str) = OPCODE_JUMPMAP[self.0 as usize] { str } else { @@ -173,282 +389,28 @@ impl OpCode { } } - #[inline(always)] - pub const fn u8(&self) -> u8 { + /// Returns the opcode as a u8. + #[inline] + pub const fn get(self) -> u8 { self.0 } -} -impl core::fmt::Display for OpCode { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if let Some(val) = OPCODE_JUMPMAP[self.0 as usize] { - f.write_str(val) - } else { - write!(f, "UNKNOWN(0x{:02x})", self.0) - } + #[inline] + #[deprecated(note = "use `new` instead")] + #[doc(hidden)] + pub const fn try_from_u8(opcode: u8) -> Option { + Self::new(opcode) + } + + #[inline] + #[deprecated(note = "use `get` instead")] + #[doc(hidden)] + pub const fn u8(self) -> u8 { + self.get() } } -pub const OPCODE_JUMPMAP: [Option<&'static str>; 256] = [ - /* 0x00 */ Some("STOP"), - /* 0x01 */ Some("ADD"), - /* 0x02 */ Some("MUL"), - /* 0x03 */ Some("SUB"), - /* 0x04 */ Some("DIV"), - /* 0x05 */ Some("SDIV"), - /* 0x06 */ Some("MOD"), - /* 0x07 */ Some("SMOD"), - /* 0x08 */ Some("ADDMOD"), - /* 0x09 */ Some("MULMOD"), - /* 0x0a */ Some("EXP"), - /* 0x0b */ Some("SIGNEXTEND"), - /* 0x0c */ None, - /* 0x0d */ None, - /* 0x0e */ None, - /* 0x0f */ None, - /* 0x10 */ Some("LT"), - /* 0x11 */ Some("GT"), - /* 0x12 */ Some("SLT"), - /* 0x13 */ Some("SGT"), - /* 0x14 */ Some("EQ"), - /* 0x15 */ Some("ISZERO"), - /* 0x16 */ Some("AND"), - /* 0x17 */ Some("OR"), - /* 0x18 */ Some("XOR"), - /* 0x19 */ Some("NOT"), - /* 0x1a */ Some("BYTE"), - /* 0x1b */ Some("SHL"), - /* 0x1c */ Some("SHR"), - /* 0x1d */ Some("SAR"), - /* 0x1e */ None, - /* 0x1f */ None, - /* 0x20 */ Some("KECCAK256"), - /* 0x21 */ None, - /* 0x22 */ None, - /* 0x23 */ None, - /* 0x24 */ None, - /* 0x25 */ None, - /* 0x26 */ None, - /* 0x27 */ None, - /* 0x28 */ None, - /* 0x29 */ None, - /* 0x2a */ None, - /* 0x2b */ None, - /* 0x2c */ None, - /* 0x2d */ None, - /* 0x2e */ None, - /* 0x2f */ None, - /* 0x30 */ Some("ADDRESS"), - /* 0x31 */ Some("BALANCE"), - /* 0x32 */ Some("ORIGIN"), - /* 0x33 */ Some("CALLER"), - /* 0x34 */ Some("CALLVALUE"), - /* 0x35 */ Some("CALLDATALOAD"), - /* 0x36 */ Some("CALLDATASIZE"), - /* 0x37 */ Some("CALLDATACOPY"), - /* 0x38 */ Some("CODESIZE"), - /* 0x39 */ Some("CODECOPY"), - /* 0x3a */ Some("GASPRICE"), - /* 0x3b */ Some("EXTCODESIZE"), - /* 0x3c */ Some("EXTCODECOPY"), - /* 0x3d */ Some("RETURNDATASIZE"), - /* 0x3e */ Some("RETURNDATACOPY"), - /* 0x3f */ Some("EXTCODEHASH"), - /* 0x40 */ Some("BLOCKHASH"), - /* 0x41 */ Some("COINBASE"), - /* 0x42 */ Some("TIMESTAMP"), - /* 0x43 */ Some("NUMBER"), - /* 0x44 */ Some("DIFFICULTY"), - /* 0x45 */ Some("GASLIMIT"), - /* 0x46 */ Some("CHAINID"), - /* 0x47 */ Some("SELFBALANCE"), - /* 0x48 */ Some("BASEFEE"), - /* 0x49 */ Some("BLOBHASH"), - /* 0x4a */ None, - /* 0x4b */ None, - /* 0x4c */ None, - /* 0x4d */ None, - /* 0x4e */ None, - /* 0x4f */ None, - /* 0x50 */ Some("POP"), - /* 0x51 */ Some("MLOAD"), - /* 0x52 */ Some("MSTORE"), - /* 0x53 */ Some("MSTORE8"), - /* 0x54 */ Some("SLOAD"), - /* 0x55 */ Some("SSTORE"), - /* 0x56 */ Some("JUMP"), - /* 0x57 */ Some("JUMPI"), - /* 0x58 */ Some("PC"), - /* 0x59 */ Some("MSIZE"), - /* 0x5a */ Some("GAS"), - /* 0x5b */ Some("JUMPDEST"), - /* 0x5c */ Some("TLOAD"), - /* 0x5d */ Some("TSTORE"), - /* 0x5e */ Some("MCOPY"), - /* 0x5f */ Some("PUSH0"), - /* 0x60 */ Some("PUSH1"), - /* 0x61 */ Some("PUSH2"), - /* 0x62 */ Some("PUSH3"), - /* 0x63 */ Some("PUSH4"), - /* 0x64 */ Some("PUSH5"), - /* 0x65 */ Some("PUSH6"), - /* 0x66 */ Some("PUSH7"), - /* 0x67 */ Some("PUSH8"), - /* 0x68 */ Some("PUSH9"), - /* 0x69 */ Some("PUSH10"), - /* 0x6a */ Some("PUSH11"), - /* 0x6b */ Some("PUSH12"), - /* 0x6c */ Some("PUSH13"), - /* 0x6d */ Some("PUSH14"), - /* 0x6e */ Some("PUSH15"), - /* 0x6f */ Some("PUSH16"), - /* 0x70 */ Some("PUSH17"), - /* 0x71 */ Some("PUSH18"), - /* 0x72 */ Some("PUSH19"), - /* 0x73 */ Some("PUSH20"), - /* 0x74 */ Some("PUSH21"), - /* 0x75 */ Some("PUSH22"), - /* 0x76 */ Some("PUSH23"), - /* 0x77 */ Some("PUSH24"), - /* 0x78 */ Some("PUSH25"), - /* 0x79 */ Some("PUSH26"), - /* 0x7a */ Some("PUSH27"), - /* 0x7b */ Some("PUSH28"), - /* 0x7c */ Some("PUSH29"), - /* 0x7d */ Some("PUSH30"), - /* 0x7e */ Some("PUSH31"), - /* 0x7f */ Some("PUSH32"), - /* 0x80 */ Some("DUP1"), - /* 0x81 */ Some("DUP2"), - /* 0x82 */ Some("DUP3"), - /* 0x83 */ Some("DUP4"), - /* 0x84 */ Some("DUP5"), - /* 0x85 */ Some("DUP6"), - /* 0x86 */ Some("DUP7"), - /* 0x87 */ Some("DUP8"), - /* 0x88 */ Some("DUP9"), - /* 0x89 */ Some("DUP10"), - /* 0x8a */ Some("DUP11"), - /* 0x8b */ Some("DUP12"), - /* 0x8c */ Some("DUP13"), - /* 0x8d */ Some("DUP14"), - /* 0x8e */ Some("DUP15"), - /* 0x8f */ Some("DUP16"), - /* 0x90 */ Some("SWAP1"), - /* 0x91 */ Some("SWAP2"), - /* 0x92 */ Some("SWAP3"), - /* 0x93 */ Some("SWAP4"), - /* 0x94 */ Some("SWAP5"), - /* 0x95 */ Some("SWAP6"), - /* 0x96 */ Some("SWAP7"), - /* 0x97 */ Some("SWAP8"), - /* 0x98 */ Some("SWAP9"), - /* 0x99 */ Some("SWAP10"), - /* 0x9a */ Some("SWAP11"), - /* 0x9b */ Some("SWAP12"), - /* 0x9c */ Some("SWAP13"), - /* 0x9d */ Some("SWAP14"), - /* 0x9e */ Some("SWAP15"), - /* 0x9f */ Some("SWAP16"), - /* 0xa0 */ Some("LOG0"), - /* 0xa1 */ Some("LOG1"), - /* 0xa2 */ Some("LOG2"), - /* 0xa3 */ Some("LOG3"), - /* 0xa4 */ Some("LOG4"), - /* 0xa5 */ None, - /* 0xa6 */ None, - /* 0xa7 */ None, - /* 0xa8 */ None, - /* 0xa9 */ None, - /* 0xaa */ None, - /* 0xab */ None, - /* 0xac */ None, - /* 0xad */ None, - /* 0xae */ None, - /* 0xaf */ None, - /* 0xb0 */ None, - /* 0xb1 */ None, - /* 0xb2 */ None, - /* 0xb3 */ None, - /* 0xb4 */ None, - /* 0xb5 */ None, - /* 0xb6 */ None, - /* 0xb7 */ None, - /* 0xb8 */ None, - /* 0xb9 */ None, - /* 0xba */ None, - /* 0xbb */ None, - /* 0xbc */ None, - /* 0xbd */ None, - /* 0xbe */ None, - /* 0xbf */ None, - /* 0xc0 */ None, - /* 0xc1 */ None, - /* 0xc2 */ None, - /* 0xc3 */ None, - /* 0xc4 */ None, - /* 0xc5 */ None, - /* 0xc6 */ None, - /* 0xc7 */ None, - /* 0xc8 */ None, - /* 0xc9 */ None, - /* 0xca */ None, - /* 0xcb */ None, - /* 0xcc */ None, - /* 0xcd */ None, - /* 0xce */ None, - /* 0xcf */ None, - /* 0xd0 */ None, - /* 0xd1 */ None, - /* 0xd2 */ None, - /* 0xd3 */ None, - /* 0xd4 */ None, - /* 0xd5 */ None, - /* 0xd6 */ None, - /* 0xd7 */ None, - /* 0xd8 */ None, - /* 0xd9 */ None, - /* 0xda */ None, - /* 0xdb */ None, - /* 0xdc */ None, - /* 0xdd */ None, - /* 0xde */ None, - /* 0xdf */ None, - /* 0xe0 */ None, - /* 0xe1 */ None, - /* 0xe2 */ None, - /* 0xe3 */ None, - /* 0xe4 */ None, - /* 0xe5 */ None, - /* 0xe6 */ None, - /* 0xe7 */ None, - /* 0xe8 */ None, - /* 0xe9 */ None, - /* 0xea */ None, - /* 0xeb */ None, - /* 0xec */ None, - /* 0xed */ None, - /* 0xee */ None, - /* 0xef */ None, - /* 0xf0 */ Some("CREATE"), - /* 0xf1 */ Some("CALL"), - /* 0xf2 */ Some("CALLCODE"), - /* 0xf3 */ Some("RETURN"), - /* 0xf4 */ Some("DELEGATECALL"), - /* 0xf5 */ Some("CREATE2"), - /* 0xf6 */ None, - /* 0xf7 */ None, - /* 0xf8 */ None, - /* 0xf9 */ None, - /* 0xfa */ Some("STATICCALL"), - /* 0xfb */ None, - /* 0xfc */ None, - /* 0xfd */ Some("REVERT"), - /* 0xfe */ Some("INVALID"), - /* 0xff */ Some("SELFDESTRUCT"), -]; - -#[derive(Debug)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct OpInfo { /// Data contains few information packed inside u32: /// IS_JUMP (1bit) | IS_GAS_BLOCK_END (1bit) | IS_PUSH (1bit) | gas (29bits) @@ -461,474 +423,341 @@ const IS_PUSH_MASK: u32 = 0x20000000; const GAS_MASK: u32 = 0x1FFFFFFF; impl OpInfo { - #[inline(always)] - pub fn is_jump(&self) -> bool { - self.data & JUMP_MASK == JUMP_MASK - } - #[inline(always)] - pub fn is_gas_block_end(&self) -> bool { - self.data & GAS_BLOCK_END_MASK == GAS_BLOCK_END_MASK - } - #[inline(always)] - pub fn is_push(&self) -> bool { - self.data & IS_PUSH_MASK == IS_PUSH_MASK - } - - #[inline(always)] - pub fn get_gas(&self) -> u32 { - self.data & GAS_MASK + /// Creates a new empty [`OpInfo`]. + pub const fn none() -> Self { + Self { data: 0 } } - pub const fn none() -> Self { + /// Creates a new dynamic gas [`OpInfo`]. + pub const fn dynamic_gas() -> Self { Self { data: 0 } } + /// Creates a new gas block end [`OpInfo`]. pub const fn gas_block_end(gas: u64) -> Self { Self { data: gas as u32 | GAS_BLOCK_END_MASK, } } - pub const fn dynamic_gas() -> Self { - Self { data: 0 } - } + /// Creates a new [`OpInfo`] with the given gas value. pub const fn gas(gas: u64) -> Self { Self { data: gas as u32 } } + + /// Creates a new push [`OpInfo`]. pub const fn push_opcode() -> Self { Self { data: gas::VERYLOW as u32 | IS_PUSH_MASK, } } + /// Creates a new jumpdest [`OpInfo`]. pub const fn jumpdest() -> Self { Self { data: JUMP_MASK | GAS_BLOCK_END_MASK, } } + + /// Returns whether the opcode is a jump. + #[inline] + pub fn is_jump(self) -> bool { + self.data & JUMP_MASK == JUMP_MASK + } + + /// Returns whether the opcode is a gas block end. + #[inline] + pub fn is_gas_block_end(self) -> bool { + self.data & GAS_BLOCK_END_MASK == GAS_BLOCK_END_MASK + } + + /// Returns whether the opcode is a push. + #[inline] + pub fn is_push(self) -> bool { + self.data & IS_PUSH_MASK == IS_PUSH_MASK + } + + /// Returns the gas cost of the opcode. + #[inline] + pub fn get_gas(self) -> u32 { + self.data & GAS_MASK + } } -macro_rules! gas_opcodee { - ($name:ident, $spec_id:expr) => { - const $name: &'static [OpInfo; 256] = &[ - /* 0x00 STOP */ OpInfo::gas_block_end(0), - /* 0x01 ADD */ OpInfo::gas(gas::VERYLOW), - /* 0x02 MUL */ OpInfo::gas(gas::LOW), - /* 0x03 SUB */ OpInfo::gas(gas::VERYLOW), - /* 0x04 DIV */ OpInfo::gas(gas::LOW), - /* 0x05 SDIV */ OpInfo::gas(gas::LOW), - /* 0x06 MOD */ OpInfo::gas(gas::LOW), - /* 0x07 SMOD */ OpInfo::gas(gas::LOW), - /* 0x08 ADDMOD */ OpInfo::gas(gas::MID), - /* 0x09 MULMOD */ OpInfo::gas(gas::MID), - /* 0x0a EXP */ OpInfo::dynamic_gas(), - /* 0x0b SIGNEXTEND */ OpInfo::gas(gas::LOW), - /* 0x0c */ OpInfo::none(), - /* 0x0d */ OpInfo::none(), - /* 0x0e */ OpInfo::none(), - /* 0x0f */ OpInfo::none(), - /* 0x10 LT */ OpInfo::gas(gas::VERYLOW), - /* 0x11 GT */ OpInfo::gas(gas::VERYLOW), - /* 0x12 SLT */ OpInfo::gas(gas::VERYLOW), - /* 0x13 SGT */ OpInfo::gas(gas::VERYLOW), - /* 0x14 EQ */ OpInfo::gas(gas::VERYLOW), - /* 0x15 ISZERO */ OpInfo::gas(gas::VERYLOW), - /* 0x16 AND */ OpInfo::gas(gas::VERYLOW), - /* 0x17 OR */ OpInfo::gas(gas::VERYLOW), - /* 0x18 XOR */ OpInfo::gas(gas::VERYLOW), - /* 0x19 NOT */ OpInfo::gas(gas::VERYLOW), - /* 0x1a BYTE */ OpInfo::gas(gas::VERYLOW), - /* 0x1b SHL */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::CONSTANTINOPLE) { - gas::VERYLOW - } else { - 0 - }), - /* 0x1c SHR */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::CONSTANTINOPLE) { - gas::VERYLOW - } else { - 0 - }), - /* 0x1d SAR */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::CONSTANTINOPLE) { - gas::VERYLOW - } else { - 0 - }), - /* 0x1e */ OpInfo::none(), - /* 0x1f */ OpInfo::none(), - /* 0x20 KECCAK256 */ OpInfo::dynamic_gas(), - /* 0x21 */ OpInfo::none(), - /* 0x22 */ OpInfo::none(), - /* 0x23 */ OpInfo::none(), - /* 0x24 */ OpInfo::none(), - /* 0x25 */ OpInfo::none(), - /* 0x26 */ OpInfo::none(), - /* 0x27 */ OpInfo::none(), - /* 0x28 */ OpInfo::none(), - /* 0x29 */ OpInfo::none(), - /* 0x2a */ OpInfo::none(), - /* 0x2b */ OpInfo::none(), - /* 0x2c */ OpInfo::none(), - /* 0x2d */ OpInfo::none(), - /* 0x2e */ OpInfo::none(), - /* 0x2f */ OpInfo::none(), - /* 0x30 ADDRESS */ OpInfo::gas(gas::BASE), - /* 0x31 BALANCE */ OpInfo::dynamic_gas(), - /* 0x32 ORIGIN */ OpInfo::gas(gas::BASE), - /* 0x33 CALLER */ OpInfo::gas(gas::BASE), - /* 0x34 CALLVALUE */ OpInfo::gas(gas::BASE), - /* 0x35 CALLDATALOAD */ OpInfo::gas(gas::VERYLOW), - /* 0x36 CALLDATASIZE */ OpInfo::gas(gas::BASE), - /* 0x37 CALLDATACOPY */ OpInfo::dynamic_gas(), - /* 0x38 CODESIZE */ OpInfo::gas(gas::BASE), - /* 0x39 CODECOPY */ OpInfo::dynamic_gas(), - /* 0x3a GASPRICE */ OpInfo::gas(gas::BASE), - /* 0x3b EXTCODESIZE */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BERLIN) { - gas::WARM_STORAGE_READ_COST // add only part of gas - } else if SpecId::enabled($spec_id, SpecId::TANGERINE) { - 700 - } else { - 20 - }), - /* 0x3c EXTCODECOPY */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BERLIN) { - gas::WARM_STORAGE_READ_COST // add only part of gas - } else if SpecId::enabled($spec_id, SpecId::TANGERINE) { - 700 - } else { - 20 - }), - /* 0x3d RETURNDATASIZE */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BYZANTIUM) { - gas::BASE - } else { - 0 - }), - /* 0x3e RETURNDATACOPY */ OpInfo::dynamic_gas(), - /* 0x3f EXTCODEHASH */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BERLIN) { - gas::WARM_STORAGE_READ_COST // add only part of gas - } else if SpecId::enabled($spec_id, SpecId::ISTANBUL) { - 700 - } else if SpecId::enabled($spec_id, SpecId::PETERSBURG) { - // constantinople - 400 - } else { - 0 // not enabled - }), - /* 0x40 BLOCKHASH */ OpInfo::gas(gas::BLOCKHASH), - /* 0x41 COINBASE */ OpInfo::gas(gas::BASE), - /* 0x42 TIMESTAMP */ OpInfo::gas(gas::BASE), - /* 0x43 NUMBER */ OpInfo::gas(gas::BASE), - /* 0x44 DIFFICULTY */ OpInfo::gas(gas::BASE), - /* 0x45 GASLIMIT */ OpInfo::gas(gas::BASE), - /* 0x46 CHAINID */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::ISTANBUL) { - gas::BASE - } else { - 0 - }), - /* 0x47 SELFBALANCE */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::ISTANBUL) { - gas::LOW - } else { - 0 - }), - /* 0x48 BASEFEE */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::LONDON) { - gas::BASE - } else { - 0 - }), - /* 0x49 BLOBHASH */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::CANCUN) { - gas::VERYLOW - } else { - 0 - }), - /* 0x4a */ OpInfo::none(), - /* 0x4b */ OpInfo::none(), - /* 0x4c */ OpInfo::none(), - /* 0x4d */ OpInfo::none(), - /* 0x4e */ OpInfo::none(), - /* 0x4f */ OpInfo::none(), - /* 0x50 POP */ OpInfo::gas(gas::BASE), - /* 0x51 MLOAD */ OpInfo::gas(gas::VERYLOW), - /* 0x52 MSTORE */ OpInfo::gas(gas::VERYLOW), - /* 0x53 MSTORE8 */ OpInfo::gas(gas::VERYLOW), - /* 0x54 SLOAD */ OpInfo::dynamic_gas(), - /* 0x55 SSTORE */ OpInfo::gas_block_end(0), - /* 0x56 JUMP */ OpInfo::gas_block_end(gas::MID), - /* 0x57 JUMPI */ OpInfo::gas_block_end(gas::HIGH), - /* 0x58 PC */ OpInfo::gas(gas::BASE), - /* 0x59 MSIZE */ OpInfo::gas(gas::BASE), - /* 0x5a GAS */ OpInfo::gas_block_end(gas::BASE), - /* 0x5b JUMPDEST */ - // gas::JUMPDEST gas is calculated in function call, - OpInfo::jumpdest(), - /* 0x5c TLOAD */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::CANCUN) { - gas::WARM_STORAGE_READ_COST - } else { - 0 - }), - /* 0x5d TSTORE */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::CANCUN) { - gas::WARM_STORAGE_READ_COST - } else { - 0 - }), - /* 0x5e MCOPY */ OpInfo::dynamic_gas(), - /* 0x5f PUSH0 */ - OpInfo::gas(if SpecId::enabled($spec_id, SpecId::SHANGHAI) { - gas::BASE - } else { - 0 - }), - /* 0x60 PUSH1 */ OpInfo::push_opcode(), - /* 0x61 PUSH2 */ OpInfo::push_opcode(), - /* 0x62 PUSH3 */ OpInfo::push_opcode(), - /* 0x63 PUSH4 */ OpInfo::push_opcode(), - /* 0x64 PUSH5 */ OpInfo::push_opcode(), - /* 0x65 PUSH6 */ OpInfo::push_opcode(), - /* 0x66 PUSH7 */ OpInfo::push_opcode(), - /* 0x67 PUSH8 */ OpInfo::push_opcode(), - /* 0x68 PUSH9 */ OpInfo::push_opcode(), - /* 0x69 PUSH10 */ OpInfo::push_opcode(), - /* 0x6a PUSH11 */ OpInfo::push_opcode(), - /* 0x6b PUSH12 */ OpInfo::push_opcode(), - /* 0x6c PUSH13 */ OpInfo::push_opcode(), - /* 0x6d PUSH14 */ OpInfo::push_opcode(), - /* 0x6e PUSH15 */ OpInfo::push_opcode(), - /* 0x6f PUSH16 */ OpInfo::push_opcode(), - /* 0x70 PUSH17 */ OpInfo::push_opcode(), - /* 0x71 PUSH18 */ OpInfo::push_opcode(), - /* 0x72 PUSH19 */ OpInfo::push_opcode(), - /* 0x73 PUSH20 */ OpInfo::push_opcode(), - /* 0x74 PUSH21 */ OpInfo::push_opcode(), - /* 0x75 PUSH22 */ OpInfo::push_opcode(), - /* 0x76 PUSH23 */ OpInfo::push_opcode(), - /* 0x77 PUSH24 */ OpInfo::push_opcode(), - /* 0x78 PUSH25 */ OpInfo::push_opcode(), - /* 0x79 PUSH26 */ OpInfo::push_opcode(), - /* 0x7a PUSH27 */ OpInfo::push_opcode(), - /* 0x7b PUSH28 */ OpInfo::push_opcode(), - /* 0x7c PUSH29 */ OpInfo::push_opcode(), - /* 0x7d PUSH30 */ OpInfo::push_opcode(), - /* 0x7e PUSH31 */ OpInfo::push_opcode(), - /* 0x7f PUSH32 */ OpInfo::push_opcode(), - /* 0x80 DUP1 */ OpInfo::gas(gas::VERYLOW), - /* 0x81 DUP2 */ OpInfo::gas(gas::VERYLOW), - /* 0x82 DUP3 */ OpInfo::gas(gas::VERYLOW), - /* 0x83 DUP4 */ OpInfo::gas(gas::VERYLOW), - /* 0x84 DUP5 */ OpInfo::gas(gas::VERYLOW), - /* 0x85 DUP6 */ OpInfo::gas(gas::VERYLOW), - /* 0x86 DUP7 */ OpInfo::gas(gas::VERYLOW), - /* 0x87 DUP8 */ OpInfo::gas(gas::VERYLOW), - /* 0x88 DUP9 */ OpInfo::gas(gas::VERYLOW), - /* 0x89 DUP10 */ OpInfo::gas(gas::VERYLOW), - /* 0x8a DUP11 */ OpInfo::gas(gas::VERYLOW), - /* 0x8b DUP12 */ OpInfo::gas(gas::VERYLOW), - /* 0x8c DUP13 */ OpInfo::gas(gas::VERYLOW), - /* 0x8d DUP14 */ OpInfo::gas(gas::VERYLOW), - /* 0x8e DUP15 */ OpInfo::gas(gas::VERYLOW), - /* 0x8f DUP16 */ OpInfo::gas(gas::VERYLOW), - /* 0x90 SWAP1 */ OpInfo::gas(gas::VERYLOW), - /* 0x91 SWAP2 */ OpInfo::gas(gas::VERYLOW), - /* 0x92 SWAP3 */ OpInfo::gas(gas::VERYLOW), - /* 0x93 SWAP4 */ OpInfo::gas(gas::VERYLOW), - /* 0x94 SWAP5 */ OpInfo::gas(gas::VERYLOW), - /* 0x95 SWAP6 */ OpInfo::gas(gas::VERYLOW), - /* 0x96 SWAP7 */ OpInfo::gas(gas::VERYLOW), - /* 0x97 SWAP8 */ OpInfo::gas(gas::VERYLOW), - /* 0x98 SWAP9 */ OpInfo::gas(gas::VERYLOW), - /* 0x99 SWAP10 */ OpInfo::gas(gas::VERYLOW), - /* 0x9a SWAP11 */ OpInfo::gas(gas::VERYLOW), - /* 0x9b SWAP12 */ OpInfo::gas(gas::VERYLOW), - /* 0x9c SWAP13 */ OpInfo::gas(gas::VERYLOW), - /* 0x9d SWAP14 */ OpInfo::gas(gas::VERYLOW), - /* 0x9e SWAP15 */ OpInfo::gas(gas::VERYLOW), - /* 0x9f SWAP16 */ OpInfo::gas(gas::VERYLOW), - /* 0xa0 LOG0 */ OpInfo::dynamic_gas(), - /* 0xa1 LOG1 */ OpInfo::dynamic_gas(), - /* 0xa2 LOG2 */ OpInfo::dynamic_gas(), - /* 0xa3 LOG3 */ OpInfo::dynamic_gas(), - /* 0xa4 LOG4 */ OpInfo::dynamic_gas(), - /* 0xa5 */ OpInfo::none(), - /* 0xa6 */ OpInfo::none(), - /* 0xa7 */ OpInfo::none(), - /* 0xa8 */ OpInfo::none(), - /* 0xa9 */ OpInfo::none(), - /* 0xaa */ OpInfo::none(), - /* 0xab */ OpInfo::none(), - /* 0xac */ OpInfo::none(), - /* 0xad */ OpInfo::none(), - /* 0xae */ OpInfo::none(), - /* 0xaf */ OpInfo::none(), - /* 0xb0 */ OpInfo::none(), - /* 0xb1 */ OpInfo::none(), - /* 0xb2 */ OpInfo::none(), - /* 0xb3 */ OpInfo::none(), - /* 0xb4 */ OpInfo::none(), - /* 0xb5 */ OpInfo::none(), - /* 0xb6 */ OpInfo::none(), - /* 0xb7 */ OpInfo::none(), - /* 0xb8 */ OpInfo::none(), - /* 0xb9 */ OpInfo::none(), - /* 0xba */ OpInfo::none(), - /* 0xbb */ OpInfo::none(), - /* 0xbc */ OpInfo::none(), - /* 0xbd */ OpInfo::none(), - /* 0xbe */ OpInfo::none(), - /* 0xbf */ OpInfo::none(), - /* 0xc0 */ OpInfo::none(), - /* 0xc1 */ OpInfo::none(), - /* 0xc2 */ OpInfo::none(), - /* 0xc3 */ OpInfo::none(), - /* 0xc4 */ OpInfo::none(), - /* 0xc5 */ OpInfo::none(), - /* 0xc6 */ OpInfo::none(), - /* 0xc7 */ OpInfo::none(), - /* 0xc8 */ OpInfo::none(), - /* 0xc9 */ OpInfo::none(), - /* 0xca */ OpInfo::none(), - /* 0xcb */ OpInfo::none(), - /* 0xcc */ OpInfo::none(), - /* 0xcd */ OpInfo::none(), - /* 0xce */ OpInfo::none(), - /* 0xcf */ OpInfo::none(), - /* 0xd0 */ OpInfo::none(), - /* 0xd1 */ OpInfo::none(), - /* 0xd2 */ OpInfo::none(), - /* 0xd3 */ OpInfo::none(), - /* 0xd4 */ OpInfo::none(), - /* 0xd5 */ OpInfo::none(), - /* 0xd6 */ OpInfo::none(), - /* 0xd7 */ OpInfo::none(), - /* 0xd8 */ OpInfo::none(), - /* 0xd9 */ OpInfo::none(), - /* 0xda */ OpInfo::none(), - /* 0xdb */ OpInfo::none(), - /* 0xdc */ OpInfo::none(), - /* 0xdd */ OpInfo::none(), - /* 0xde */ OpInfo::none(), - /* 0xdf */ OpInfo::none(), - /* 0xe0 */ OpInfo::none(), - /* 0xe1 */ OpInfo::none(), - /* 0xe2 */ OpInfo::none(), - /* 0xe3 */ OpInfo::none(), - /* 0xe4 */ OpInfo::none(), - /* 0xe5 */ OpInfo::none(), - /* 0xe6 */ OpInfo::none(), - /* 0xe7 */ OpInfo::none(), - /* 0xe8 */ OpInfo::none(), - /* 0xe9 */ OpInfo::none(), - /* 0xea */ OpInfo::none(), - /* 0xeb */ OpInfo::none(), - /* 0xec */ OpInfo::none(), - /* 0xed */ OpInfo::none(), - /* 0xee */ OpInfo::none(), - /* 0xef */ OpInfo::none(), - /* 0xf0 CREATE */ OpInfo::gas_block_end(0), - /* 0xf1 CALL */ OpInfo::gas_block_end(0), - /* 0xf2 CALLCODE */ OpInfo::gas_block_end(0), - /* 0xf3 RETURN */ OpInfo::gas_block_end(0), - /* 0xf4 DELEGATECALL */ OpInfo::gas_block_end(0), - /* 0xf5 CREATE2 */ OpInfo::gas_block_end(0), - /* 0xf6 */ OpInfo::none(), - /* 0xf7 */ OpInfo::none(), - /* 0xf8 */ OpInfo::none(), - /* 0xf9 */ OpInfo::none(), - /* 0xfa STATICCALL */ OpInfo::gas_block_end(0), - /* 0xfb */ OpInfo::none(), - /* 0xfc */ OpInfo::none(), - /* 0xfd REVERT */ OpInfo::gas_block_end(0), - /* 0xfe INVALID */ OpInfo::gas_block_end(0), - /* 0xff SELFDESTRUCT */ OpInfo::gas_block_end(0), - ]; - }; +const fn opcode_gas_info(opcode: u8, spec: SpecId) -> OpInfo { + match opcode { + STOP => OpInfo::gas_block_end(0), + ADD => OpInfo::gas(gas::VERYLOW), + MUL => OpInfo::gas(gas::LOW), + SUB => OpInfo::gas(gas::VERYLOW), + DIV => OpInfo::gas(gas::LOW), + SDIV => OpInfo::gas(gas::LOW), + MOD => OpInfo::gas(gas::LOW), + SMOD => OpInfo::gas(gas::LOW), + ADDMOD => OpInfo::gas(gas::MID), + MULMOD => OpInfo::gas(gas::MID), + EXP => OpInfo::dynamic_gas(), + SIGNEXTEND => OpInfo::gas(gas::LOW), + + LT => OpInfo::gas(gas::VERYLOW), + GT => OpInfo::gas(gas::VERYLOW), + SLT => OpInfo::gas(gas::VERYLOW), + SGT => OpInfo::gas(gas::VERYLOW), + EQ => OpInfo::gas(gas::VERYLOW), + ISZERO => OpInfo::gas(gas::VERYLOW), + AND => OpInfo::gas(gas::VERYLOW), + OR => OpInfo::gas(gas::VERYLOW), + XOR => OpInfo::gas(gas::VERYLOW), + NOT => OpInfo::gas(gas::VERYLOW), + BYTE => OpInfo::gas(gas::VERYLOW), + SHL => OpInfo::gas(if SpecId::enabled(spec, SpecId::CONSTANTINOPLE) { + gas::VERYLOW + } else { + 0 + }), + SHR => OpInfo::gas(if SpecId::enabled(spec, SpecId::CONSTANTINOPLE) { + gas::VERYLOW + } else { + 0 + }), + SAR => OpInfo::gas(if SpecId::enabled(spec, SpecId::CONSTANTINOPLE) { + gas::VERYLOW + } else { + 0 + }), + + KECCAK256 => OpInfo::dynamic_gas(), + + ADDRESS => OpInfo::gas(gas::BASE), + BALANCE => OpInfo::dynamic_gas(), + ORIGIN => OpInfo::gas(gas::BASE), + CALLER => OpInfo::gas(gas::BASE), + CALLVALUE => OpInfo::gas(gas::BASE), + CALLDATALOAD => OpInfo::gas(gas::VERYLOW), + CALLDATASIZE => OpInfo::gas(gas::BASE), + CALLDATACOPY => OpInfo::dynamic_gas(), + CODESIZE => OpInfo::gas(gas::BASE), + CODECOPY => OpInfo::dynamic_gas(), + GASPRICE => OpInfo::gas(gas::BASE), + EXTCODESIZE => OpInfo::gas(if SpecId::enabled(spec, SpecId::BERLIN) { + gas::WARM_STORAGE_READ_COST // add only part of gas + } else if SpecId::enabled(spec, SpecId::TANGERINE) { + 700 + } else { + 20 + }), + EXTCODECOPY => OpInfo::gas(if SpecId::enabled(spec, SpecId::BERLIN) { + gas::WARM_STORAGE_READ_COST // add only part of gas + } else if SpecId::enabled(spec, SpecId::TANGERINE) { + 700 + } else { + 20 + }), + RETURNDATASIZE => OpInfo::gas(if SpecId::enabled(spec, SpecId::BYZANTIUM) { + gas::BASE + } else { + 0 + }), + RETURNDATACOPY => OpInfo::dynamic_gas(), + EXTCODEHASH => OpInfo::gas(if SpecId::enabled(spec, SpecId::BERLIN) { + gas::WARM_STORAGE_READ_COST // add only part of gas + } else if SpecId::enabled(spec, SpecId::ISTANBUL) { + 700 + } else if SpecId::enabled(spec, SpecId::PETERSBURG) { + 400 // constantinople + } else { + 0 // not enabled + }), + BLOCKHASH => OpInfo::gas(gas::BLOCKHASH), + COINBASE => OpInfo::gas(gas::BASE), + TIMESTAMP => OpInfo::gas(gas::BASE), + NUMBER => OpInfo::gas(gas::BASE), + DIFFICULTY => OpInfo::gas(gas::BASE), + GASLIMIT => OpInfo::gas(gas::BASE), + CHAINID => OpInfo::gas(if SpecId::enabled(spec, SpecId::ISTANBUL) { + gas::BASE + } else { + 0 + }), + SELFBALANCE => OpInfo::gas(if SpecId::enabled(spec, SpecId::ISTANBUL) { + gas::LOW + } else { + 0 + }), + BASEFEE => OpInfo::gas(if SpecId::enabled(spec, SpecId::LONDON) { + gas::BASE + } else { + 0 + }), + BLOBHASH => OpInfo::gas(if SpecId::enabled(spec, SpecId::CANCUN) { + gas::VERYLOW + } else { + 0 + }), + + POP => OpInfo::gas(gas::BASE), + MLOAD => OpInfo::gas(gas::VERYLOW), + MSTORE => OpInfo::gas(gas::VERYLOW), + MSTORE8 => OpInfo::gas(gas::VERYLOW), + SLOAD => OpInfo::dynamic_gas(), + SSTORE => OpInfo::gas_block_end(0), + JUMP => OpInfo::gas_block_end(gas::MID), + JUMPI => OpInfo::gas_block_end(gas::HIGH), + PC => OpInfo::gas(gas::BASE), + MSIZE => OpInfo::gas(gas::BASE), + GAS => OpInfo::gas_block_end(gas::BASE), + // gas::JUMPDEST gas is calculated in function call + JUMPDEST => OpInfo::jumpdest(), + TLOAD => OpInfo::gas(if SpecId::enabled(spec, SpecId::CANCUN) { + gas::WARM_STORAGE_READ_COST + } else { + 0 + }), + TSTORE => OpInfo::gas(if SpecId::enabled(spec, SpecId::CANCUN) { + gas::WARM_STORAGE_READ_COST + } else { + 0 + }), + MCOPY => OpInfo::dynamic_gas(), + + PUSH0 => OpInfo::gas(if SpecId::enabled(spec, SpecId::SHANGHAI) { + gas::BASE + } else { + 0 + }), + PUSH1 => OpInfo::push_opcode(), + PUSH2 => OpInfo::push_opcode(), + PUSH3 => OpInfo::push_opcode(), + PUSH4 => OpInfo::push_opcode(), + PUSH5 => OpInfo::push_opcode(), + PUSH6 => OpInfo::push_opcode(), + PUSH7 => OpInfo::push_opcode(), + PUSH8 => OpInfo::push_opcode(), + PUSH9 => OpInfo::push_opcode(), + PUSH10 => OpInfo::push_opcode(), + PUSH11 => OpInfo::push_opcode(), + PUSH12 => OpInfo::push_opcode(), + PUSH13 => OpInfo::push_opcode(), + PUSH14 => OpInfo::push_opcode(), + PUSH15 => OpInfo::push_opcode(), + PUSH16 => OpInfo::push_opcode(), + PUSH17 => OpInfo::push_opcode(), + PUSH18 => OpInfo::push_opcode(), + PUSH19 => OpInfo::push_opcode(), + PUSH20 => OpInfo::push_opcode(), + PUSH21 => OpInfo::push_opcode(), + PUSH22 => OpInfo::push_opcode(), + PUSH23 => OpInfo::push_opcode(), + PUSH24 => OpInfo::push_opcode(), + PUSH25 => OpInfo::push_opcode(), + PUSH26 => OpInfo::push_opcode(), + PUSH27 => OpInfo::push_opcode(), + PUSH28 => OpInfo::push_opcode(), + PUSH29 => OpInfo::push_opcode(), + PUSH30 => OpInfo::push_opcode(), + PUSH31 => OpInfo::push_opcode(), + PUSH32 => OpInfo::push_opcode(), + + DUP1 => OpInfo::gas(gas::VERYLOW), + DUP2 => OpInfo::gas(gas::VERYLOW), + DUP3 => OpInfo::gas(gas::VERYLOW), + DUP4 => OpInfo::gas(gas::VERYLOW), + DUP5 => OpInfo::gas(gas::VERYLOW), + DUP6 => OpInfo::gas(gas::VERYLOW), + DUP7 => OpInfo::gas(gas::VERYLOW), + DUP8 => OpInfo::gas(gas::VERYLOW), + DUP9 => OpInfo::gas(gas::VERYLOW), + DUP10 => OpInfo::gas(gas::VERYLOW), + DUP11 => OpInfo::gas(gas::VERYLOW), + DUP12 => OpInfo::gas(gas::VERYLOW), + DUP13 => OpInfo::gas(gas::VERYLOW), + DUP14 => OpInfo::gas(gas::VERYLOW), + DUP15 => OpInfo::gas(gas::VERYLOW), + DUP16 => OpInfo::gas(gas::VERYLOW), + + SWAP1 => OpInfo::gas(gas::VERYLOW), + SWAP2 => OpInfo::gas(gas::VERYLOW), + SWAP3 => OpInfo::gas(gas::VERYLOW), + SWAP4 => OpInfo::gas(gas::VERYLOW), + SWAP5 => OpInfo::gas(gas::VERYLOW), + SWAP6 => OpInfo::gas(gas::VERYLOW), + SWAP7 => OpInfo::gas(gas::VERYLOW), + SWAP8 => OpInfo::gas(gas::VERYLOW), + SWAP9 => OpInfo::gas(gas::VERYLOW), + SWAP10 => OpInfo::gas(gas::VERYLOW), + SWAP11 => OpInfo::gas(gas::VERYLOW), + SWAP12 => OpInfo::gas(gas::VERYLOW), + SWAP13 => OpInfo::gas(gas::VERYLOW), + SWAP14 => OpInfo::gas(gas::VERYLOW), + SWAP15 => OpInfo::gas(gas::VERYLOW), + SWAP16 => OpInfo::gas(gas::VERYLOW), + + LOG0 => OpInfo::dynamic_gas(), + LOG1 => OpInfo::dynamic_gas(), + LOG2 => OpInfo::dynamic_gas(), + LOG3 => OpInfo::dynamic_gas(), + LOG4 => OpInfo::dynamic_gas(), + + CREATE => OpInfo::gas_block_end(0), + CALL => OpInfo::gas_block_end(0), + CALLCODE => OpInfo::gas_block_end(0), + RETURN => OpInfo::gas_block_end(0), + DELEGATECALL => OpInfo::gas_block_end(0), + CREATE2 => OpInfo::gas_block_end(0), + + STATICCALL => OpInfo::gas_block_end(0), + + REVERT => OpInfo::gas_block_end(0), + INVALID => OpInfo::gas_block_end(0), + SELFDESTRUCT => OpInfo::gas_block_end(0), + + _ => OpInfo::none(), + } +} + +const fn make_gas_table(spec: SpecId) -> [OpInfo; 256] { + let mut table = [OpInfo::none(); 256]; + let mut i = 0; + while i < 256 { + table[i] = opcode_gas_info(i as u8, spec); + i += 1; + } + table } +/// Returns a lookup table of opcode gas info for the given [`SpecId`]. +#[inline] pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] { - match spec_id { - SpecId::FRONTIER => { - gas_opcodee!(FRONTIER, SpecId::FRONTIER); - FRONTIER - } - SpecId::FRONTIER_THAWING => { - gas_opcodee!(FRONTIER_THAWING, SpecId::FRONTIER_THAWING); - FRONTIER_THAWING - } - SpecId::HOMESTEAD => { - gas_opcodee!(HOMESTEAD, SpecId::HOMESTEAD); - HOMESTEAD - } - SpecId::DAO_FORK => { - gas_opcodee!(DAO_FORK, SpecId::DAO_FORK); - DAO_FORK - } - SpecId::TANGERINE => { - gas_opcodee!(TANGERINE, SpecId::TANGERINE); - TANGERINE - } - SpecId::SPURIOUS_DRAGON => { - gas_opcodee!(SPURIOUS_DRAGON, SpecId::SPURIOUS_DRAGON); - SPURIOUS_DRAGON - } - SpecId::BYZANTIUM => { - gas_opcodee!(BYZANTIUM, SpecId::BYZANTIUM); - BYZANTIUM - } - SpecId::CONSTANTINOPLE => { - gas_opcodee!(CONSTANTINOPLE, SpecId::CONSTANTINOPLE); - CONSTANTINOPLE - } - SpecId::PETERSBURG => { - gas_opcodee!(PETERSBURG, SpecId::PETERSBURG); - PETERSBURG - } - SpecId::ISTANBUL => { - gas_opcodee!(ISTANBUL, SpecId::ISTANBUL); - ISTANBUL - } - SpecId::MUIR_GLACIER => { - gas_opcodee!(MUIRGLACIER, SpecId::MUIR_GLACIER); - MUIRGLACIER - } - SpecId::BERLIN => { - gas_opcodee!(BERLIN, SpecId::BERLIN); - BERLIN - } - SpecId::LONDON => { - gas_opcodee!(LONDON, SpecId::LONDON); - LONDON - } - SpecId::ARROW_GLACIER => { - gas_opcodee!(ARROW_GLACIER, SpecId::ARROW_GLACIER); - ARROW_GLACIER - } - SpecId::GRAY_GLACIER => { - gas_opcodee!(GRAY_GLACIER, SpecId::GRAY_GLACIER); - GRAY_GLACIER - } - SpecId::MERGE => { - gas_opcodee!(MERGE, SpecId::MERGE); - MERGE - } - SpecId::SHANGHAI => { - gas_opcodee!(SHANGAI, SpecId::SHANGHAI); - SHANGAI - } - SpecId::CANCUN => { - gas_opcodee!(CANCUN, SpecId::CANCUN); - CANCUN - } - SpecId::LATEST => { - gas_opcodee!(LATEST, SpecId::LATEST); - LATEST - } + macro_rules! gas_maps { + ($($id:ident),* $(,)?) => { + match spec_id {$( + SpecId::$id => { + const TABLE: &[OpInfo; 256] = &make_gas_table(SpecId::$id); + TABLE + } + )*} + }; } + + gas_maps!( + FRONTIER, + FRONTIER_THAWING, + HOMESTEAD, + DAO_FORK, + TANGERINE, + SPURIOUS_DRAGON, + BYZANTIUM, + CONSTANTINOPLE, + PETERSBURG, + ISTANBUL, + MUIR_GLACIER, + BERLIN, + LONDON, + ARROW_GLACIER, + GRAY_GLACIER, + MERGE, + SHANGHAI, + CANCUN, + LATEST, + ) } diff --git a/crates/interpreter/src/instructions/stack.rs b/crates/interpreter/src/instructions/stack.rs index 5fbe237f3e..c9cff77fe1 100644 --- a/crates/interpreter/src/instructions/stack.rs +++ b/crates/interpreter/src/instructions/stack.rs @@ -1,21 +1,21 @@ -use crate::gas; -use crate::InstructionResult; -use revm_primitives::{Spec, SpecId::SHANGHAI, U256}; - -use crate::{interpreter::Interpreter, Host}; +use crate::{ + gas, + primitives::{Spec, U256}, + Host, InstructionResult, Interpreter, +}; pub fn pop(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::BASE); - if let Some(ret) = interpreter.stack.reduce_one() { - interpreter.instruction_result = ret; + if let Err(result) = interpreter.stack.pop() { + interpreter.instruction_result = result; } } -/// EIP-3855: PUSH0 instruction -/// Introduce a new instruction which pushes the constant value 0 onto the stack +/// EIP-3855: PUSH0 instruction +/// +/// Introduce a new instruction which pushes the constant value 0 onto the stack. pub fn push0(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // EIP-3855: PUSH0 instruction - check!(interpreter, SPEC::enabled(SHANGHAI)); + check!(interpreter, SHANGHAI); gas!(interpreter, gas::BASE); if let Err(result) = interpreter.stack.push(U256::ZERO) { interpreter.instruction_result = result; @@ -26,27 +26,27 @@ pub fn push(interpreter: &mut Interpreter, _host: &mut dyn Host) gas!(interpreter, gas::VERYLOW); let start = interpreter.instruction_pointer; // Safety: In Analysis we appended needed bytes for bytecode so that we are safe to just add without - // checking if it is out of bound. This makes both of our unsafe blocks safe to do. - if let Some(ret) = interpreter + // checking if it is out of bound. This makes both of our unsafes block safe to do. + if let Err(result) = interpreter .stack .push_slice::(unsafe { core::slice::from_raw_parts(start, N) }) { - interpreter.instruction_result = ret; + interpreter.instruction_result = result; return; } - interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.add(N) }; + interpreter.instruction_pointer = unsafe { start.add(N) }; } pub fn dup(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); - if let Some(ret) = interpreter.stack.dup::() { - interpreter.instruction_result = ret; + if let Err(result) = interpreter.stack.dup::() { + interpreter.instruction_result = result; } } pub fn swap(interpreter: &mut Interpreter, _host: &mut dyn Host) { gas!(interpreter, gas::VERYLOW); - if let Some(ret) = interpreter.stack.swap::() { - interpreter.instruction_result = ret; + if let Err(result) = interpreter.stack.swap::() { + interpreter.instruction_result = result; } } diff --git a/crates/interpreter/src/instructions/system.rs b/crates/interpreter/src/instructions/system.rs index 03ad16f344..d39ada9de0 100644 --- a/crates/interpreter/src/instructions/system.rs +++ b/crates/interpreter/src/instructions/system.rs @@ -1,21 +1,19 @@ use crate::{ gas, - interpreter::Interpreter, - primitives::{keccak256, Spec, SpecId::*, B256, KECCAK_EMPTY, U256}, - Host, InstructionResult, + primitives::{Spec, B256, KECCAK_EMPTY, U256}, + Host, InstructionResult, Interpreter, }; -use core::cmp::min; -pub fn calculate_keccak256(interpreter: &mut Interpreter, _host: &mut dyn Host) { +pub fn keccak256(interpreter: &mut Interpreter, _host: &mut dyn Host) { pop!(interpreter, from, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len); gas_or_fail!(interpreter, gas::keccak256_cost(len as u64)); let hash = if len == 0 { KECCAK_EMPTY } else { - let from = as_usize_or_fail!(interpreter, from, InstructionResult::InvalidOperandOOG); + let from = as_usize_or_fail!(interpreter, from); memory_resize!(interpreter, from, len); - keccak256(interpreter.memory.get_slice(from, len)) + crate::primitives::keccak256(interpreter.memory.slice(from, len)) }; push_b256!(interpreter, hash); @@ -38,16 +36,12 @@ pub fn codesize(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn codecopy(interpreter: &mut Interpreter, _host: &mut dyn Host) { pop!(interpreter, memory_offset, code_offset, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len); gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); if len == 0 { return; } - let memory_offset = as_usize_or_fail!( - interpreter, - memory_offset, - InstructionResult::InvalidOperandOOG - ); + let memory_offset = as_usize_or_fail!(interpreter, memory_offset); let code_offset = as_usize_saturated!(code_offset); memory_resize!(interpreter, memory_offset, len); @@ -66,7 +60,7 @@ pub fn calldataload(interpreter: &mut Interpreter, _host: &mut dyn Host) { let index = as_usize_saturated!(index); let load = if index < interpreter.contract.input.len() { - let have_bytes = min(interpreter.contract.input.len() - index, 32); + let n = 32.min(interpreter.contract.input.len() - index); let mut bytes = [0u8; 32]; bytes[..have_bytes].copy_from_slice(&interpreter.contract.input[index..index + have_bytes]); B256::new(bytes) @@ -74,7 +68,7 @@ pub fn calldataload(interpreter: &mut Interpreter, _host: &mut dyn Host) { B256::ZERO }; - push_b256!(interpreter, load); + push!(interpreter, load); } pub fn calldatasize(interpreter: &mut Interpreter, _host: &mut dyn Host) { @@ -89,16 +83,12 @@ pub fn callvalue(interpreter: &mut Interpreter, _host: &mut dyn Host) { pub fn calldatacopy(interpreter: &mut Interpreter, _host: &mut dyn Host) { pop!(interpreter, memory_offset, data_offset, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len); gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); if len == 0 { return; } - let memory_offset = as_usize_or_fail!( - interpreter, - memory_offset, - InstructionResult::InvalidOperandOOG - ); + let memory_offset = as_usize_or_fail!(interpreter, memory_offset); let data_offset = as_usize_saturated!(data_offset); memory_resize!(interpreter, memory_offset, len); @@ -108,21 +98,21 @@ pub fn calldatacopy(interpreter: &mut Interpreter, _host: &mut dyn Host) { .set_data(memory_offset, data_offset, len, &interpreter.contract.input); } +/// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY pub fn returndatasize(interpreter: &mut Interpreter, _host: &mut dyn Host) { + check!(interpreter, BYZANTIUM); gas!(interpreter, gas::BASE); - // EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY - check!(interpreter, SPEC::enabled(BYZANTIUM)); push!( interpreter, U256::from(interpreter.return_data_buffer.len()) ); } +/// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY pub fn returndatacopy(interpreter: &mut Interpreter, _host: &mut dyn Host) { - // EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY - check!(interpreter, SPEC::enabled(BYZANTIUM)); + check!(interpreter, BYZANTIUM); pop!(interpreter, memory_offset, offset, len); - let len = as_usize_or_fail!(interpreter, len, InstructionResult::InvalidOperandOOG); + let len = as_usize_or_fail!(interpreter, len); gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); let data_offset = as_usize_saturated!(offset); let (data_end, overflow) = data_offset.overflowing_add(len); @@ -131,11 +121,7 @@ pub fn returndatacopy(interpreter: &mut Interpreter, _host: &mut dyn return; } if len != 0 { - let memory_offset = as_usize_or_fail!( - interpreter, - memory_offset, - InstructionResult::InvalidOperandOOG - ); + let memory_offset = as_usize_or_fail!(interpreter, memory_offset); memory_resize!(interpreter, memory_offset, len); interpreter.memory.set( memory_offset, diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index 9f21ad5202..324b795b16 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -1,21 +1,16 @@ pub mod analysis; mod contract; -pub(crate) mod memory; +pub mod memory; mod stack; +use crate::primitives::{Bytes, Spec}; +use crate::{alloc::boxed::Box, opcode::eval, Gas, Host, InstructionResult}; + pub use analysis::BytecodeLocked; pub use contract::Contract; pub use memory::Memory; pub use stack::{Stack, STACK_LIMIT}; -use crate::primitives::{Bytes, Spec}; -use crate::{ - alloc::boxed::Box, - instructions::{eval, InstructionResult}, - Gas, Host, -}; -use core::ops::Range; - pub const CALL_STACK_LIMIT: u64 = 1024; /// EIP-170: Contract code size limit @@ -26,60 +21,59 @@ pub const MAX_CODE_SIZE: usize = 0x6000; /// EIP-3860: Limit and meter initcode pub const MAX_INITCODE_SIZE: usize = 2 * MAX_CODE_SIZE; +#[derive(Debug)] pub struct Interpreter { - /// Instruction pointer. + /// Contract information and invoking data. + pub contract: Box, + /// The current instruction pointer. pub instruction_pointer: *const u8, - /// Return is main control flag, it tell us if we should continue interpreter or break from it + /// The execution control flag. If this is not set to `Continue`, the interpreter will stop + /// execution. pub instruction_result: InstructionResult, - /// left gas. Memory gas can be found in Memory field. + /// The gas state. pub gas: Gas, - /// Memory. + /// The memory. pub memory: Memory, - /// Stack. + /// The stack. pub stack: Stack, - /// After call returns, its return data is saved here. + /// The return data buffer for internal calls. pub return_data_buffer: Bytes, - /// Return value. - pub return_range: Range, - /// Is interpreter call static. + /// The offset into `self.memory` of the return data. + /// + /// This value must be ignored if `self.return_len` is 0. + pub return_offset: usize, + /// The length of the return data. + pub return_len: usize, + /// Whether the interpreter is in "staticcall" mode, meaning no state changes can happen. pub is_static: bool, - /// Contract information and invoking data - pub contract: Box, /// Memory limit. See [`crate::CfgEnv`]. #[cfg(feature = "memory_limit")] pub memory_limit: u64, } impl Interpreter { - /// Current opcode - pub fn current_opcode(&self) -> u8 { - unsafe { *self.instruction_pointer } - } - - /// Create new interpreter + /// Instantiates a new interpreter. + #[inline] pub fn new(contract: Box, gas_limit: u64, is_static: bool) -> Self { - #[cfg(not(feature = "memory_limit"))] - { - Self { - instruction_pointer: contract.bytecode.as_ptr(), - return_range: Range::default(), - memory: Memory::new(), - stack: Stack::new(), - return_data_buffer: Bytes::new(), - contract, - instruction_result: InstructionResult::Continue, - is_static, - gas: Gas::new(gas_limit), - } - } - - #[cfg(feature = "memory_limit")] - { - Self::new_with_memory_limit(contract, gas_limit, is_static, u64::MAX) + Self { + instruction_pointer: contract.bytecode.as_ptr(), + contract, + instruction_result: InstructionResult::Continue, + gas: Gas::new(gas_limit), + memory: Memory::new(), + stack: Stack::new(), + return_data_buffer: Bytes::new(), + return_offset: 0, + return_len: 0, + is_static, + #[cfg(feature = "memory_limit")] + memory_limit: u64::MAX, } } + /// Instantiates a new interpreter with the given memory limit. #[cfg(feature = "memory_limit")] + #[inline] pub fn new_with_memory_limit( contract: Box, gas_limit: u64, @@ -87,38 +81,43 @@ impl Interpreter { memory_limit: u64, ) -> Self { Self { - instruction_pointer: contract.bytecode.as_ptr(), - return_range: Range::default(), - memory: Memory::new(), - stack: Stack::new(), - return_data_buffer: Bytes::new(), - contract, - instruction_result: InstructionResult::Continue, - is_static, - gas: Gas::new(gas_limit), memory_limit, + ..Self::new(contract, gas_limit, is_static) } } + /// Returns the opcode at the current instruction pointer. + #[inline] + pub fn current_opcode(&self) -> u8 { + unsafe { *self.instruction_pointer } + } + + /// Returns a reference to the contract. + #[inline] pub fn contract(&self) -> &Contract { &self.contract } + /// Returns a reference to the interpreter's gas state. + #[inline] pub fn gas(&self) -> &Gas { &self.gas } - /// Reference of interpreter memory. + /// Returns a reference to the interpreter's memory. + #[inline] pub fn memory(&self) -> &Memory { &self.memory } - /// Reference of interpreter stack. + /// Returns a reference to the interpreter's stack. + #[inline] pub fn stack(&self) -> &Stack { &self.stack } - /// Return a reference of the program counter. + /// Returns the current program counter. + #[inline] pub fn program_counter(&self) -> usize { // Safety: this is just subtraction of pointers, it is safe to do. unsafe { @@ -127,55 +126,60 @@ impl Interpreter { } } - /// Execute next instruction + /// Executes the instruction at the current instruction pointer. #[inline(always)] - pub fn step(&mut self, host: &mut H) { + pub fn step(&mut self, host: &mut dyn Host) { // step. let opcode = unsafe { *self.instruction_pointer }; // Safety: In analysis we are doing padding of bytecode so that we are sure that last // byte instruction is STOP so we are safe to just increment program_counter bcs on last instruction // it will do noop and just stop execution of this contract self.instruction_pointer = unsafe { self.instruction_pointer.offset(1) }; - eval::(opcode, self, host); + eval::(opcode, self, host); } - /// loop steps until we are finished with execution + /// Executes the interpreter until it returns or stops. pub fn run(&mut self, host: &mut H) -> InstructionResult { while self.instruction_result == InstructionResult::Continue { - self.step::(host) + self.step::(host); } self.instruction_result } - /// loop steps until we are finished with execution + /// Executes the interpreter until it returns or stops. Same as `run` but with + /// calls to the [`Host::step`] and [`Host::step_end`] callbacks. pub fn run_inspect(&mut self, host: &mut H) -> InstructionResult { while self.instruction_result == InstructionResult::Continue { // step - let ret = host.step(self); - if ret != InstructionResult::Continue { - return ret; + let result = host.step(self); + if result != InstructionResult::Continue { + return result; } - self.step::(host); + + self.step::(host); // step ends - let ret = host.step_end(self, self.instruction_result); - if ret != InstructionResult::Continue { - return ret; + let result = host.step_end(self, self.instruction_result); + if result != InstructionResult::Continue { + return result; } } self.instruction_result } - /// Copy and get the return value of the interpreter, if any. + /// Returns a copy of the interpreter's return value, if any. + #[inline] pub fn return_value(&self) -> Bytes { - // if start is usize max it means that our return len is zero and we need to return empty - if self.return_range.start == usize::MAX { - Bytes::new() + self.return_value_slice().to_vec().into() + } + + /// Returns a reference to the interpreter's return value, if any. + #[inline] + pub fn return_value_slice(&self) -> &[u8] { + if self.return_len == 0 { + &[] } else { - Bytes::copy_from_slice(self.memory.get_slice( - self.return_range.start, - self.return_range.end - self.return_range.start, - )) + self.memory.slice(self.return_offset, self.return_len) } } } diff --git a/crates/interpreter/src/interpreter/analysis.rs b/crates/interpreter/src/interpreter/analysis.rs index 7f6e735b39..d26fa85676 100644 --- a/crates/interpreter/src/interpreter/analysis.rs +++ b/crates/interpreter/src/interpreter/analysis.rs @@ -1,13 +1,10 @@ use crate::opcode; -use crate::primitives::{Bytecode, BytecodeState, Bytes}; -use alloc::sync::Arc; -// use bitvec::order::Lsb0; -// use bitvec::prelude::bitvec; -// use bitvec::vec::BitVec; -use revm_primitives::{ +use crate::primitives::{ bitvec::prelude::{bitvec, BitVec, Lsb0}, - JumpMap, + keccak256, Bytecode, BytecodeState, Bytes, JumpMap, B256, KECCAK_EMPTY, }; +use alloc::sync::Arc; +use core::fmt; /// Perform bytecode analysis. /// @@ -68,7 +65,21 @@ pub struct BytecodeLocked { jump_map: JumpMap, } +impl fmt::Debug for BytecodeLocked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BytecodeLocked") + .field("bytecode", &self.bytecode) + .field("len", &self.len) + .field( + "jump_map", + &crate::primitives::hex::encode(self.jump_map.as_slice()), + ) + .finish() + } +} + impl Default for BytecodeLocked { + #[inline] fn default() -> Self { Bytecode::default() .try_into() @@ -79,6 +90,7 @@ impl Default for BytecodeLocked { impl TryFrom for BytecodeLocked { type Error = (); + #[inline] fn try_from(bytecode: Bytecode) -> Result { if let BytecodeState::Analysed { len, jump_map } = bytecode.state { Ok(BytecodeLocked { @@ -93,17 +105,35 @@ impl TryFrom for BytecodeLocked { } impl BytecodeLocked { + /// Returns a raw pointer to the underlying byte slice. + #[inline] pub fn as_ptr(&self) -> *const u8 { self.bytecode.as_ptr() } + + /// Returns the length of the bytecode. + #[inline] pub fn len(&self) -> usize { self.len } + /// Returns whether the bytecode is empty. + #[inline] pub fn is_empty(&self) -> bool { self.len == 0 } + /// Calculate hash of the bytecode. + #[inline] + pub fn hash_slow(&self) -> B256 { + if self.is_empty() { + KECCAK_EMPTY + } else { + keccak256(self.original_bytecode_slice()) + } + } + + #[inline] pub fn unlock(self) -> Bytecode { Bytecode { bytecode: self.bytecode, @@ -113,14 +143,28 @@ impl BytecodeLocked { }, } } + + /// Returns the bytecode as a byte slice. + #[inline] pub fn bytecode(&self) -> &[u8] { - self.bytecode.as_ref() + &self.bytecode } + /// Returns the original bytecode as a byte slice. + #[inline] pub fn original_bytecode_slice(&self) -> &[u8] { - &self.bytecode.as_ref()[..self.len] + match self.bytecode.get(..self.len) { + Some(slice) => slice, + None => debug_unreachable!( + "original_bytecode_slice OOB: {} > {}", + self.len, + self.bytecode.len() + ), + } } + /// Returns a reference to the jump map. + #[inline] pub fn jump_map(&self) -> &JumpMap { &self.jump_map } diff --git a/crates/interpreter/src/interpreter/contract.rs b/crates/interpreter/src/interpreter/contract.rs index 669225139b..bc7a248097 100644 --- a/crates/interpreter/src/interpreter/contract.rs +++ b/crates/interpreter/src/interpreter/contract.rs @@ -1,9 +1,8 @@ use super::analysis::{to_analysed, BytecodeLocked}; -use crate::primitives::{Address, Bytecode, Bytes, U256}; +use crate::primitives::{Address, Bytecode, Bytes, Env, TransactTo, B256, U256}; use crate::CallContext; -use revm_primitives::{Env, TransactTo, B256}; -#[derive(Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Contract { /// Contracts data pub input: Bytes, @@ -21,6 +20,8 @@ pub struct Contract { } impl Contract { + /// Instantiates a new contract by analyzing the given bytecode. + #[inline] pub fn new( input: Bytes, bytecode: Bytecode, @@ -41,7 +42,8 @@ impl Contract { } } - /// Create new contract from environment + /// Creates a new contract from the given [`Env`]. + #[inline] pub fn new_env(env: &Env, bytecode: Bytecode, hash: B256) -> Self { let contract_address = match env.tx.transact_to { TransactTo::Call(caller) => caller, @@ -57,10 +59,8 @@ impl Contract { ) } - pub fn is_valid_jump(&self, possition: usize) -> bool { - self.bytecode.jump_map().is_valid(possition) - } - + /// Creates a new contract from the given [`CallContext`]. + #[inline] pub fn new_with_context( input: Bytes, bytecode: Bytecode, @@ -76,4 +76,10 @@ impl Contract { call_context.apparent_value, ) } + + /// Returns whether the given position is a valid jump destination. + #[inline] + pub fn is_valid_jump(&self, pos: usize) -> bool { + self.bytecode.jump_map().is_valid(pos) + } } diff --git a/crates/interpreter/src/interpreter/memory.rs b/crates/interpreter/src/interpreter/memory.rs index baedbc7143..aa3a43fdb0 100644 --- a/crates/interpreter/src/interpreter/memory.rs +++ b/crates/interpreter/src/interpreter/memory.rs @@ -2,18 +2,28 @@ use crate::primitives::U256; use alloc::vec::Vec; use core::{ cmp::min, + fmt, ops::{BitAnd, Not}, }; /// A sequential memory. It uses Rust's `Vec` for internal /// representation. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Memory { data: Vec, } +impl fmt::Debug for Memory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Memory") + .field("data", &crate::primitives::hex::encode(&self.data)) + .finish() + } +} + impl Default for Memory { + #[inline] fn default() -> Self { Self { data: Vec::with_capacity(4 * 1024), // took it from evmone @@ -23,104 +33,154 @@ impl Default for Memory { impl Memory { /// Create a new memory with the given limit. + #[inline] pub fn new() -> Self { Self { data: Vec::with_capacity(4 * 1024), // took it from evmone } } + #[deprecated = "Use `len` instead"] + #[doc(hidden)] + #[inline] pub fn effective_len(&self) -> usize { - self.data.len() + self.len() } - /// Get the length of the current memory range. + /// Returns the length of the current memory range. + #[inline] pub fn len(&self) -> usize { self.data.len() } - /// Return true if current effective memory range is zero. + /// Returns true if current memory range length is zero. + #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Return the full memory. + /// Return a reference to the full memory. + #[inline] pub fn data(&self) -> &Vec { &self.data } /// Consumes the type and returns the full memory. + #[inline] pub fn into_data(self) -> Vec { self.data } /// Shrinks the capacity of the data buffer as much as possible. + #[inline] pub fn shrink_to_fit(&mut self) { self.data.shrink_to_fit() } - /// Resize the memory. Assume that we already checked if - /// we have enought gas to resize this vector and that we made new_size as multiply of 32 + /// Resizes the stack in-place so that then length is equal to `new_size`. + /// + /// `new_size` should be a multiple of 32. + #[inline] pub fn resize(&mut self, new_size: usize) { self.data.resize(new_size, 0); } - /// Get memory region at given offset. Don't check offset and size + /// Returns a byte slice of the memory region at the given offset. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn slice(&self, offset: usize, size: usize) -> &[u8] { + match self.data.get(offset..offset + size) { + Some(slice) => slice, + None => debug_unreachable!("slice OOB: {offset}..{size}; len: {}", self.len()), + } + } + + #[deprecated = "use `slice` instead"] #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] pub fn get_slice(&self, offset: usize, size: usize) -> &[u8] { - &self.data[offset..offset + size] + self.slice(offset, size) + } + + /// Returns a mutable byte slice of the memory region at the given offset. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn slice_mut(&mut self, offset: usize, size: usize) -> &mut [u8] { + let _len = self.len(); + match self.data.get_mut(offset..offset + size) { + Some(slice) => slice, + None => debug_unreachable!("slice_mut OOB: {offset}..{size}; len: {_len}"), + } } - /// Set memory region at given offset + /// Sets the `byte` at the given `index`. /// - /// # Safety - /// The caller is responsible for checking the offset and value + /// Panics when `index` is out of bounds. #[inline(always)] - pub unsafe fn set_byte(&mut self, index: usize, byte: u8) { - *self.data.get_mut(index).unwrap() = byte; + #[cfg_attr(debug_assertions, track_caller)] + pub fn set_byte(&mut self, index: usize, byte: u8) { + match self.data.get_mut(index) { + Some(b) => *b = byte, + None => debug_unreachable!("set_byte OOB: {index}; len: {}", self.len()), + } } + /// Sets the given `value` to the memory region at the given `offset`. + /// + /// Panics on out of bounds. #[inline(always)] - pub fn set_u256(&mut self, index: usize, value: U256) { - self.data[index..index + 32].copy_from_slice(&value.to_be_bytes::<{ U256::BYTES }>()); + #[cfg_attr(debug_assertions, track_caller)] + pub fn set_u256(&mut self, offset: usize, value: U256) { + self.set(offset, &value.to_be_bytes::<32>()); } - /// Set memory region at given offset. The offset and value are already checked + /// Set memory region at given `offset`. + /// + /// Panics on out of bounds. #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] pub fn set(&mut self, offset: usize, value: &[u8]) { if !value.is_empty() { - self.data[offset..(value.len() + offset)].copy_from_slice(value); + self.slice_mut(offset, value.len()).copy_from_slice(value); } } /// Set memory from data. Our memory offset+len is expected to be correct but we - /// are doing bound checks on data/data_offset/len and zeroing parts that is not copied. + /// are doing bound checks on data/data_offeset/len and zeroing parts that is not copied. + /// + /// Panics on out of bounds. #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] pub fn set_data(&mut self, memory_offset: usize, data_offset: usize, len: usize, data: &[u8]) { if data_offset >= data.len() { // nullify all memory slots - for i in &mut self.data[memory_offset..memory_offset + len] { - *i = 0; - } + self.slice_mut(memory_offset, len).fill(0); return; } let data_end = min(data_offset + len, data.len()); - let memory_data_end = memory_offset + (data_end - data_offset); - self.data[memory_offset..memory_data_end].copy_from_slice(&data[data_offset..data_end]); + let data_len = data_end - data_offset; + debug_assert!(data_offset < data.len() && data_end <= data.len()); + let data = unsafe { data.get_unchecked(data_offset..data_end) }; + self.slice_mut(memory_offset, data_len) + .copy_from_slice(data); // nullify rest of memory slots // Safety: Memory is assumed to be valid. And it is commented where that assumption is made - for i in &mut self.data[memory_data_end..memory_offset + len] { - *i = 0; - } + self.slice_mut(memory_offset + data_len, len - data_len) + .fill(0); } - /// In memory copy given a src, dst, and length + /// Copies elements from one part of the memory to another part of itself. /// - /// # Safety - /// The caller is responsible to check that we resized memory properly. + /// Panics on out of bounds. #[inline(always)] - pub fn copy(&mut self, dst: usize, src: usize, length: usize) { - self.data.copy_within(src..src + length, dst); + #[cfg_attr(debug_assertions, track_caller)] + pub fn copy(&mut self, dst: usize, src: usize, len: usize) { + self.data.copy_within(src..src + len, dst); } } @@ -133,9 +193,8 @@ pub(crate) fn next_multiple_of_32(x: usize) -> Option { #[cfg(test)] mod tests { - use crate::Memory; - use super::next_multiple_of_32; + use crate::Memory; #[test] fn test_copy() { @@ -151,7 +210,7 @@ mod tests { memory.copy(5, 0, 4); // Verify the copied data - let copied_data = memory.get_slice(5, 4); + let copied_data = memory.slice(5, 4); assert_eq!(copied_data, &[1, 2, 3, 4]); } diff --git a/crates/interpreter/src/interpreter/stack.rs b/crates/interpreter/src/interpreter/stack.rs index e18db34a38..0a3f8a2edb 100644 --- a/crates/interpreter/src/interpreter/stack.rs +++ b/crates/interpreter/src/interpreter/stack.rs @@ -3,7 +3,9 @@ use crate::{ InstructionResult, }; use alloc::vec::Vec; +use core::fmt; +/// The EVM stack limit, in number of items. pub const STACK_LIMIT: usize = 1024; /// EVM stack. @@ -13,201 +15,175 @@ pub struct Stack { data: Vec, } -#[cfg(feature = "std")] -impl std::fmt::Display for Stack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - if self.data.is_empty() { - f.write_str("[]")?; - } else { - f.write_str("[")?; - for i in self.data[..self.data.len() - 1].iter() { - f.write_str(&i.to_string())?; +impl fmt::Display for Stack { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + for (i, x) in self.data.iter().enumerate() { + if i > 0 { f.write_str(", ")?; } - f.write_str(&self.data.last().unwrap().to_string())?; - f.write_str("]")?; + write!(f, "{x}")?; } - Ok(()) + f.write_str("]") } } impl Default for Stack { + #[inline] fn default() -> Self { Self::new() } } impl Stack { - /// Create a new stack with given limit. + /// Instantiate a new stack with the [default stack limit][STACK_LIMIT]. + #[inline] pub fn new() -> Self { Self { - // Safety: A lot of functions assumes that capacity is STACK_LIMIT + // Safety: [`Self::push`] assumes that capacity is STACK_LIMIT data: Vec::with_capacity(STACK_LIMIT), } } + /// Returns the length of the stack in words. #[inline] - /// Stack length. pub fn len(&self) -> usize { self.data.len() } + /// Returns whether the stack is empty. #[inline] - /// Whether the stack is empty. pub fn is_empty(&self) -> bool { self.data.is_empty() } + /// Returns the underlying data of the stack. #[inline] - /// Stack data. pub fn data(&self) -> &Vec { &self.data } - #[inline(always)] - pub fn reduce_one(&mut self) -> Option { - let len = self.data.len(); - if len < 1 { - return Some(InstructionResult::StackUnderflow); - } - unsafe { - self.data.set_len(len - 1); - } - None - } - + /// Removes the topmost element from the stack and returns it, or `StackUnderflow` if it is + /// empty. #[inline] - /// Pop a value from the stack. If the stack is already empty, returns the - /// `StackUnderflow` error. pub fn pop(&mut self) -> Result { self.data.pop().ok_or(InstructionResult::StackUnderflow) } - #[inline(always)] - /// Pops a value from the stack, returning it. + /// Removes the topmost element from the stack and returns it. /// /// # Safety - /// The caller is responsible to check length of array + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] pub unsafe fn pop_unsafe(&mut self) -> U256 { - let mut len = self.data.len(); - len -= 1; - self.data.set_len(len); - *self.data.get_unchecked(len) + self.data.pop().unwrap_unchecked() } - #[inline(always)] /// Peeks the top of the stack. /// /// # Safety - /// The caller is responsible to check length of array + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] pub unsafe fn top_unsafe(&mut self) -> &mut U256 { let len = self.data.len(); self.data.get_unchecked_mut(len - 1) } - #[inline(always)] /// Pop the topmost value, returning the value and the new topmost value. /// /// # Safety - /// The caller is responsible to check length of array + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] pub unsafe fn pop_top_unsafe(&mut self) -> (U256, &mut U256) { - let mut len = self.data.len(); - let pop = *self.data.get_unchecked(len - 1); - len -= 1; - self.data.set_len(len); - - (pop, self.data.get_unchecked_mut(len - 1)) + let pop = self.pop_unsafe(); + let top = self.top_unsafe(); + (pop, top) } - #[inline(always)] - /// Pops 2 values from the stack and returns them, in addition to the new topmost value. + /// Pops 2 values from the stack. /// /// # Safety - /// The caller is responsible to check length of array - pub unsafe fn pop2_top_unsafe(&mut self) -> (U256, U256, &mut U256) { - let mut len = self.data.len(); - let pop1 = *self.data.get_unchecked(len - 1); - len -= 2; - let pop2 = *self.data.get_unchecked(len); - self.data.set_len(len); - - (pop1, pop2, self.data.get_unchecked_mut(len - 1)) + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] + pub unsafe fn pop2_unsafe(&mut self) -> (U256, U256) { + let pop1 = self.pop_unsafe(); + let pop2 = self.pop_unsafe(); + (pop1, pop2) } - #[inline(always)] - /// Pops 2 values from the stack. + /// Pops 2 values from the stack and returns them, in addition to the new topmost value. /// /// # Safety - /// The caller is responsible to check length of array - pub unsafe fn pop2_unsafe(&mut self) -> (U256, U256) { - let mut len = self.data.len(); - len -= 2; - self.data.set_len(len); - ( - *self.data.get_unchecked(len + 1), - *self.data.get_unchecked(len), - ) + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] + pub unsafe fn pop2_top_unsafe(&mut self) -> (U256, U256, &mut U256) { + let pop1 = self.pop_unsafe(); + let pop2 = self.pop_unsafe(); + let top = self.top_unsafe(); + + (pop1, pop2, top) } - #[inline(always)] /// Pops 3 values from the stack. /// /// # Safety - /// The caller is responsible to check length of array + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] pub unsafe fn pop3_unsafe(&mut self) -> (U256, U256, U256) { - let mut len = self.data.len(); - len -= 3; - self.data.set_len(len); - ( - *self.data.get_unchecked(len + 2), - *self.data.get_unchecked(len + 1), - *self.data.get_unchecked(len), - ) + let pop1 = self.pop_unsafe(); + let pop2 = self.pop_unsafe(); + let pop3 = self.pop_unsafe(); + + (pop1, pop2, pop3) } - #[inline(always)] /// Pops 4 values from the stack. /// /// # Safety - /// The caller is responsible to check length of array + /// + /// The caller is responsible for checking the length of the stack. + #[inline(always)] pub unsafe fn pop4_unsafe(&mut self) -> (U256, U256, U256, U256) { - let mut len = self.data.len(); - len -= 4; - self.data.set_len(len); - ( - *self.data.get_unchecked(len + 3), - *self.data.get_unchecked(len + 2), - *self.data.get_unchecked(len + 1), - *self.data.get_unchecked(len), - ) + let pop1 = self.pop_unsafe(); + let pop2 = self.pop_unsafe(); + let pop3 = self.pop_unsafe(); + let pop4 = self.pop_unsafe(); + + (pop1, pop2, pop3, pop4) } - #[inline] /// Push a new value into the stack. If it will exceed the stack limit, /// returns `StackOverflow` error and leaves the stack unchanged. + #[inline(always)] pub fn push_b256(&mut self, value: B256) -> Result<(), InstructionResult> { - if self.data.len() + 1 > STACK_LIMIT { - return Err(InstructionResult::StackOverflow); - } - self.data.push(U256::from_be_bytes(value.0)); - Ok(()) + self.push(value.into()) } - #[inline] - /// Push a new value into the stack. If it will exceed the stack limit, - /// returns `StackOverflow` error and leaves the stack unchanged. + /// Push a new value onto the stack. + /// + /// If it will exceed the stack limit, returns `StackOverflow` error and leaves the stack + /// unchanged. + #[inline(always)] pub fn push(&mut self, value: U256) -> Result<(), InstructionResult> { - if self.data.len() + 1 > STACK_LIMIT { + // allows the compiler to optimize out the `Vec::push` capacity check + assume!(self.data.capacity() == STACK_LIMIT); + if self.data.len() == STACK_LIMIT { return Err(InstructionResult::StackOverflow); } self.data.push(value); Ok(()) } - #[inline] /// Peek a value at given index for the stack, where the top of /// the stack is at index `0`. If the index is too large, /// `StackError::Underflow` is returned. + #[inline(always)] pub fn peek(&self, no_from_top: usize) -> Result { if self.data.len() > no_from_top { Ok(self.data[self.data.len() - no_from_top - 1]) @@ -216,44 +192,45 @@ impl Stack { } } + /// Duplicates the `N`th value from the top of the stack. #[inline(always)] - pub fn dup(&mut self) -> Option { + pub fn dup(&mut self) -> Result<(), InstructionResult> { let len = self.data.len(); if len < N { - Some(InstructionResult::StackUnderflow) + Err(InstructionResult::StackUnderflow) } else if len + 1 > STACK_LIMIT { - Some(InstructionResult::StackOverflow) + Err(InstructionResult::StackOverflow) } else { // Safety: check for out of bounds is done above and it makes this safe to do. unsafe { *self.data.get_unchecked_mut(len) = *self.data.get_unchecked(len - N); self.data.set_len(len + 1); } - None + Ok(()) } } + /// Swaps the topmost value with the `N`th value from the top. #[inline(always)] - pub fn swap(&mut self) -> Option { + pub fn swap(&mut self) -> Result<(), InstructionResult> { let len = self.data.len(); if len <= N { - return Some(InstructionResult::StackUnderflow); + return Err(InstructionResult::StackUnderflow); } - // Safety: length is checked before so we are okay to switch bytes in unsafe way. - unsafe { - let pa: *mut U256 = self.data.get_unchecked_mut(len - 1); - let pb: *mut U256 = self.data.get_unchecked_mut(len - 1 - N); - core::ptr::swap(pa, pb); - } - None + let last = len - 1; + self.data.swap(last, last - N); + Ok(()) } - /// push slice onto memory it is expected to be max 32 bytes and be contains inside B256 + /// Push a slice of bytes of `N` length onto the stack. + /// + /// If it will exceed the stack limit, returns `StackOverflow` error and leaves the stack + /// unchanged. #[inline(always)] - pub fn push_slice(&mut self, slice: &[u8]) -> Option { + pub fn push_slice(&mut self, slice: &[u8]) -> Result<(), InstructionResult> { let new_len = self.data.len() + 1; if new_len > STACK_LIMIT { - return Some(InstructionResult::StackOverflow); + return Err(InstructionResult::StackOverflow); } let slot; @@ -302,13 +279,13 @@ impl Stack { } } } - None + Ok(()) } - #[inline] /// Set a value at given index for the stack, where the top of the /// stack is at index `0`. If the index is too large, /// `StackError::Underflow` is returned. + #[inline] pub fn set(&mut self, no_from_top: usize, val: U256) -> Result<(), InstructionResult> { if self.data.len() > no_from_top { let len = self.data.len(); diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index a82107503d..7ecbd729d2 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -1,15 +1,17 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + +#[macro_use] +mod macros; + pub mod gas; mod host; pub mod inner_models; pub mod instruction_result; -mod instructions; +pub mod instructions; mod interpreter; -extern crate alloc; -extern crate core; - pub(crate) const USE_GAS: bool = !cfg!(feature = "no_gas_measuring"); // Reexport primary types. @@ -17,9 +19,11 @@ pub use gas::Gas; pub use host::{DummyHost, Host}; pub use inner_models::*; pub use instruction_result::InstructionResult; -pub use instructions::opcode::{self, OpCode, OPCODE_JUMPMAP}; -pub use interpreter::*; -pub use interpreter::{BytecodeLocked, Contract, Interpreter, Memory, Stack}; +pub use instructions::{opcode, Instruction, OpCode, OPCODE_JUMPMAP}; +pub use interpreter::{ + analysis, BytecodeLocked, Contract, Interpreter, Memory, Stack, CALL_STACK_LIMIT, + MAX_CODE_SIZE, MAX_INITCODE_SIZE, +}; #[doc(inline)] pub use revm_primitives as primitives; diff --git a/crates/interpreter/src/macros.rs b/crates/interpreter/src/macros.rs new file mode 100644 index 0000000000..d0c1c6c080 --- /dev/null +++ b/crates/interpreter/src/macros.rs @@ -0,0 +1,23 @@ +macro_rules! debug_unreachable { + ($($t:tt)*) => { + if cfg!(debug_assertions) { + unreachable!($($t)*); + } else { + unsafe { core::hint::unreachable_unchecked() }; + } + }; +} + +macro_rules! assume { + ($e:expr $(,)?) => { + if !$e { + debug_unreachable!(stringify!($e)); + } + }; + + ($e:expr, $($t:tt)+) => { + if !$e { + debug_unreachable!($($t)+); + } + }; +} diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index 72b67ade17..6c2e9fcdfe 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -8,7 +8,7 @@ mod bn128; mod hash; mod identity; #[cfg(feature = "std")] -mod kzg_point_evaluation; +pub mod kzg_point_evaluation; mod modexp; mod secp256k1; diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index e75523b602..bbbf7c776d 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -21,13 +21,19 @@ pub const MAX_INITCODE_SIZE: usize = 2 * MAX_CODE_SIZE; pub const PRECOMPILE3: Address = Address::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); // EIP-4844 constants -/// Maximum consumable blob gas for data blobs per block. -pub const MAX_BLOB_GAS_PER_BLOCK: u64 = 6 * GAS_PER_BLOB; -/// Target consumable blob gas for data blobs per block (for 1559-like pricing). -pub const TARGET_BLOB_GAS_PER_BLOCK: u64 = 3 * GAS_PER_BLOB; /// Gas consumption of a single data blob (== blob byte size). pub const GAS_PER_BLOB: u64 = 1 << 17; +/// Target number of the blob per block. +pub const TARGET_BLOB_NUMBER_PER_BLOCK: u64 = 3; +/// Max number of blobs per block +pub const MAX_BLOB_NUMBER_PER_BLOCK: u64 = 2 * TARGET_BLOB_NUMBER_PER_BLOCK; +/// Maximum consumable blob gas for data blobs per block. +pub const MAX_BLOB_GAS_PER_BLOCK: u64 = MAX_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB; +/// Target consumable blob gas for data blobs per block (for 1559-like pricing). +pub const TARGET_BLOB_GAS_PER_BLOCK: u64 = TARGET_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB; /// Minimum gas price for data blobs. pub const MIN_BLOB_GASPRICE: u64 = 1; /// Controls the maximum rate of change for blob gas price. pub const BLOB_GASPRICE_UPDATE_FRACTION: u64 = 3338477; +/// First version of the blob. +pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index f92b796c56..007b1db0f6 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -1,6 +1,7 @@ use crate::{ - alloc::vec::Vec, calc_blob_fee, Account, Address, Bytes, EVMError, InvalidTransaction, Spec, - SpecId, B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_INITCODE_SIZE, U256, + alloc::vec::Vec, calc_blob_fee, Account, Address, EVMError, InvalidTransaction, Spec, SpecId, + B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_BLOB_NUMBER_PER_BLOCK, MAX_INITCODE_SIZE, U256, + VERSIONED_HASH_VERSION_KZG, }; use core::cmp::{min, Ordering}; @@ -106,7 +107,8 @@ pub struct TxEnv { /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 pub gas_priority_fee: Option, - /// The list of blob versioned hashes. + /// The list of blob versioned hashes. Per EIP there should be at least + /// one blob present if [`Self::max_fee_per_blob_gas`] is `Some`. /// /// Incorporated as part of the Cancun upgrade via [EIP-4844]. /// @@ -415,7 +417,7 @@ impl Env { if let Some(priority_fee) = self.tx.gas_priority_fee { if priority_fee > self.tx.gas_price { // or gas_max_fee for eip1559 - return Err(InvalidTransaction::GasMaxFeeGreaterThanPriorityFee); + return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee); } } let basefee = self.block.basefee; @@ -458,11 +460,40 @@ impl Env { // - For CANCUN and later, check that the gas price is not more than the tx max // - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set if SPEC::enabled(SpecId::CANCUN) { + // Presence of max_fee_per_blob_gas means that this is blob transaction. if let Some(max) = self.tx.max_fee_per_blob_gas { + // ensure that the user was willing to at least pay the current blob gasprice let price = self.block.get_blob_gasprice().expect("already checked"); if U256::from(price) > max { return Err(InvalidTransaction::BlobGasPriceGreaterThanMax); } + + // there must be at least one blob + // assert len(tx.blob_versioned_hashes) > 0 + if self.tx.blob_hashes.is_empty() { + return Err(InvalidTransaction::EmptyBlobs); + } + + // The field `to` deviates slightly from the semantics with the exception + // that it MUST NOT be nil and therefore must always represent + // a 20-byte address. This means that blob transactions cannot + // have the form of a create transaction. + if self.tx.transact_to.is_create() { + return Err(InvalidTransaction::BlobCreateTransaction); + } + + // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG + for blob in self.tx.blob_hashes.iter() { + if blob[0] != VERSIONED_HASH_VERSION_KZG { + return Err(InvalidTransaction::BlobVersionNotSupported); + } + } + + // ensure the total blob gas spent is at most equal to the limit + // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK + if self.tx.blob_hashes.len() > MAX_BLOB_NUMBER_PER_BLOCK as usize { + return Err(InvalidTransaction::TooManyBlobs); + } } } else { if !self.tx.blob_hashes.is_empty() { diff --git a/crates/primitives/src/result.rs b/crates/primitives/src/result.rs index 766fe20524..6bce8984bf 100644 --- a/crates/primitives/src/result.rs +++ b/crates/primitives/src/result.rs @@ -148,9 +148,21 @@ impl From for EVMError { #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum InvalidTransaction { - GasMaxFeeGreaterThanPriorityFee, + /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields: + /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee. + /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip". + /// + /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`. + PriorityFeeGreaterThanMaxFee, + /// EIP-1559: `gas_price` is less than `basefee`. GasPriceLessThanBasefee, + /// `gas_limit` in the tx is bigger than `block_gas_limit`. CallerGasLimitMoreThanBlock, + /// Initial gas for a Call is bigger than `gas_limit`. + /// + /// Initial gas for a Call contains: + /// - initial stipend gas + /// - gas for access list and input data CallGasCostMoreThanGasLimit, /// EIP-3607 Reject transactions from senders with deployed code RejectCallerWithCode, @@ -173,6 +185,7 @@ pub enum InvalidTransaction { }, /// EIP-3860: Limit and meter initcode CreateInitcodeSizeLimit, + /// Transaction chain id does not match the config chain id. InvalidChainId, /// Access list is not supported for blocks before the Berlin hardfork. AccessListNotSupported, @@ -182,6 +195,15 @@ pub enum InvalidTransaction { BlobVersionedHashesNotSupported, /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun. BlobGasPriceGreaterThanMax, + /// There should be at least one blob in Blob transaction. + EmptyBlobs, + /// Blob transaction can't be a create transaction. + /// `to` must be present + BlobCreateTransaction, + /// Transaction has more then [`crate::MAX_BLOB_NUMBER_PER_BLOCK`] blobs + TooManyBlobs, + /// Blob transaction contains a versioned hash with an incorrect version + BlobVersionNotSupported, } /// Reason a transaction successfully completed. diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 8fcd4eabbc..c047a8a342 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -1,4 +1,4 @@ -use crate::primitives::{specification, EVMError, EVMResult, Env, ExecutionResult, SpecId}; +use crate::primitives::{specification, EVMError, EVMResult, Env, ExecutionResult}; use crate::{ db::{Database, DatabaseCommit, DatabaseRef}, evm_impl::{EVMImpl, Transact}, @@ -198,29 +198,6 @@ impl EVM { } } -pub fn to_precompile_id(spec_id: SpecId) -> revm_precompile::SpecId { - match spec_id { - SpecId::FRONTIER - | SpecId::FRONTIER_THAWING - | SpecId::HOMESTEAD - | SpecId::DAO_FORK - | SpecId::TANGERINE - | SpecId::SPURIOUS_DRAGON => revm_precompile::SpecId::HOMESTEAD, - SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { - revm_precompile::SpecId::BYZANTIUM - } - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => revm_precompile::SpecId::ISTANBUL, - SpecId::BERLIN - | SpecId::LONDON - | SpecId::ARROW_GLACIER - | SpecId::GRAY_GLACIER - | SpecId::MERGE - | SpecId::SHANGHAI - | SpecId::CANCUN - | SpecId::LATEST => revm_precompile::SpecId::BERLIN, - } -} - pub fn evm_inner<'a, DB: Database, const INSPECT: bool>( env: &'a mut Env, db: &'a mut DB, @@ -232,7 +209,7 @@ pub fn evm_inner<'a, DB: Database, const INSPECT: bool>( db, env, insp, - Precompiles::new(to_precompile_id($spec::SPEC_ID)).clone(), + Precompiles::new(revm_precompile::SpecId::from_spec_id($spec::SPEC_ID)).clone(), )) as Box + 'a> }; } diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index c65b300ace..2071f3255d 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -106,7 +106,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact &env.tx.access_list, ); - // Additonal check to see if limit is big enought to cover initial gas. + // Additional check to see if limit is big enough to cover initial gas. if initial_gas_spend > env.tx.gas_limit { return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into()); } diff --git a/crates/revm/src/inspector/gas.rs b/crates/revm/src/inspector/gas.rs index 3700ab355c..93bccc7c56 100644 --- a/crates/revm/src/inspector/gas.rs +++ b/crates/revm/src/inspector/gas.rs @@ -239,7 +239,7 @@ mod tests { for (pc, gas) in inspector.gas_remaining_steps { println!( "{pc} {} {gas:?}", - OpCode::try_from_u8(bytecode.bytes()[pc]).unwrap().as_str(), + OpCode::new(bytecode.bytes()[pc]).unwrap().as_str(), ); } } diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index e2b3c1710a..6b93b8705b 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -22,7 +22,7 @@ pub use db::{ }; pub use db::{Database, DatabaseCommit, InMemoryDB}; -pub use evm::{evm_inner, new, to_precompile_id, EVM}; +pub use evm::{evm_inner, new, EVM}; pub use evm_impl::{EVMData, EVMImpl, Transact}; pub use journaled_state::{is_precompile, JournalCheckpoint, JournalEntry, JournaledState};