diff --git a/Cargo.lock b/Cargo.lock index ce3337b1ac427..6a4d8f4b83047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13224,6 +13224,7 @@ dependencies = [ "polkavm 0.29.1", "polkavm-common 0.29.0", "pretty_assertions", + "proptest", "rand 0.8.5", "rand_pcg", "revm", diff --git a/Cargo.toml b/Cargo.toml index d5e9c32cb3a49..2db4df21bfe1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1181,6 +1181,7 @@ procfs = { version = "0.16.0" } prometheus = { version = "0.13.0", default-features = false } prometheus-endpoint = { path = "substrate/utils/prometheus", default-features = false, package = "substrate-prometheus-endpoint" } prometheus-parse = { version = "0.2.2" } +proptest = { version = "1" } prost = { version = "0.12.4" } prost-build = { version = "0.13.2" } pyroscope = { version = "0.5.8" } diff --git a/prdoc/pr_9818.prdoc b/prdoc/pr_9818.prdoc new file mode 100644 index 0000000000000..5b1a778aeff31 --- /dev/null +++ b/prdoc/pr_9818.prdoc @@ -0,0 +1,28 @@ +title: '[pallet-revive] revm refactor' +doc: +- audience: Runtime Dev + description: | + This PR refactors the REVM implementation in `pallet-revive`, reducing technical debt and decoupling it from specific REVM versions. This enables easier integration with other projects, such as Foundry, with dependencies limited to REVM's [`Bytecode`](https://docs.rs/revm/latest/revm/bytecode/struct.Bytecode.html). + + **Background:** + Many of REVM's generics and trait abstractions were unused or ignored to avoid bugs. The pallet interacts with the host via the `Ext` trait, rendering other REVM abstractions unnecessary. + + Previously unused REVM components included: + - `Host` trait: Unused, relied on the `DummyHost` mock. + - Gas field: Not used, as the pallet uses its own accounting. + - Methods from `InputsTr`: Most methods unused or had panic implementations. + - `Spec`: Only maintaining the latest fork per runtime. + + **Key Changes:** + - Interpreter: Now a minimal struct with only necessary fields. + - Stack: Uses `sp_core::U256` for better integration. + - Memory: Simplified to needed methods only. + - Instructions: Rewritten with simple signatures, using match statements instead of function pointer tables. Gas charging uses `EVMGas` wrapped in `Token`. The interpreter loop has been significantly simplified. + - Error Handling: Failed opcodes now return `Halt::Err(DispatchError)`, removing previous error conversions. + +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch + diff --git a/prdoc/pr_9887.prdoc b/prdoc/pr_9887.prdoc new file mode 100644 index 0000000000000..e658208e98d9a --- /dev/null +++ b/prdoc/pr_9887.prdoc @@ -0,0 +1,7 @@ +title: Rve/9565 pallet revive evm backend ensure memory dos safety2 +doc: +- audience: Runtime Dev + description: fixes https://github.com/paritytech/polkadot-sdk/issues/9565 +crates: +- name: pallet-revive + bump: patch diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index de536727ea91d..01e9f76b16240 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -74,6 +74,7 @@ pallet-proxy = { workspace = true, default-features = true } pallet-revive-fixtures = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } pallet-utility = { workspace = true, default-features = true } +proptest = { workspace = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol b/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol index 8ae8199f62fac..f3da8fcf801d0 100644 --- a/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol +++ b/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol @@ -6,7 +6,7 @@ contract TransactionInfo { return tx.origin; } - function gasprice() public view returns (uint256) { + function gasprice() public view returns (uint64) { return uint64(tx.gasprice); } diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 44e712ba7ea7e..f0aa4ce7b4072 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -34,7 +34,7 @@ use crate::{ storage::WriteOutcome, vm::{ evm, - evm::{instructions::instruction_table, EVMInterpreter}, + evm::{instructions::host, Interpreter}, pvm, }, Pallet as Contracts, *, @@ -54,13 +54,7 @@ use frame_system::RawOrigin; use pallet_revive_uapi::{ pack_hi_lo, precompiles::system::ISystem, CallFlags, ReturnErrorCode, StorageFlags, }; -use revm::{ - bytecode::{opcode::EXTCODECOPY, Bytecode}, - interpreter::{ - host::DummyHost, interpreter_types::MemoryTr, InstructionContext, Interpreter, SharedMemory, - }, - primitives, -}; +use revm::bytecode::Bytecode; use sp_consensus_aura::AURA_ENGINE_ID; use sp_consensus_babe::{ digests::{PreDigest, PrimaryPreDigest}, @@ -2367,7 +2361,7 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> { let module = VmBinaryModule::evm_noop(r); - let inputs = evm::EVMInputs::new(vec![]); + let inputs = vec![]; let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone())); let mut setup = CallSetup::::new(module); @@ -2472,38 +2466,22 @@ mod benchmarks { let mut setup = CallSetup::::new(module); let contract = setup.contract(); - let mut address: [u8; 32] = [0; 32]; - address[12..].copy_from_slice(&contract.address.0); - let (mut ext, _) = setup.ext(); - let mut interpreter: Interpreter> = Interpreter { - extend: &mut ext, - input: Default::default(), - bytecode: Default::default(), - gas: Default::default(), - stack: Default::default(), - return_data: Default::default(), - memory: SharedMemory::new(), - runtime_flag: Default::default(), - }; - - let table = instruction_table::<'_, _>(); - let extcodecopy_fn = table[EXTCODECOPY as usize]; + let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext); // Setup stack for extcodecopy instruction: [address, dest_offset, offset, size] - let _ = interpreter.stack.push(primitives::U256::from(n)); - let _ = interpreter.stack.push(primitives::U256::from(0u32)); - let _ = interpreter.stack.push(primitives::U256::from(0u32)); - let _ = interpreter.stack.push(primitives::U256::from_be_bytes(address)); - - let mut host = DummyHost {}; - let context = InstructionContext { interpreter: &mut interpreter, host: &mut host }; + let _ = interpreter.stack.push(U256::from(n)); + let _ = interpreter.stack.push(U256::from(0u32)); + let _ = interpreter.stack.push(U256::from(0u32)); + let _ = interpreter.stack.push(contract.address); + let result; #[block] { - extcodecopy_fn(context); + result = host::extcodecopy(&mut interpreter); } + assert!(result.is_continue()); assert_eq!( *interpreter.memory.slice(0..n as usize), PristineCode::::get(contract.info()?.code_hash).unwrap()[0..n as usize], diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 15217c4ae8fdc..b62faaba7ca33 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -30,7 +30,7 @@ use crate::{ Pallet as Contracts, RuntimeCosts, LOG_TARGET, }; use alloc::vec::Vec; -use core::{fmt::Debug, marker::PhantomData, mem}; +use core::{fmt::Debug, marker::PhantomData, mem, ops::ControlFlow}; use frame_support::{ crypto::ecdsa::ECDSAExt, dispatch::DispatchResult, @@ -290,6 +290,14 @@ pub trait PrecompileExt: sealing::Sealed { .adjust_gas(charged, RuntimeCosts::Precompile(actual_weight)); } + /// Charges the gas meter with the given token or halts execution if not enough gas is left. + fn charge_or_halt>( + &mut self, + token: Tok, + ) -> ControlFlow { + self.gas_meter_mut().charge_or_halt(token) + } + /// Call (possibly transferring some amount of funds) into the specified account. fn call( &mut self, diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 3793fe24067b5..038e58b3d93cc 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -14,9 +14,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -use crate::{exec::ExecError, weights::WeightInfo, Config, Error}; -use core::marker::PhantomData; +use crate::{exec::ExecError, vm::evm::Halt, weights::WeightInfo, Config, Error}; +use core::{marker::PhantomData, ops::ControlFlow}; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, weights::Weight, @@ -219,16 +218,13 @@ impl GasMeter { Ok(ChargedAmount(amount)) } - /// Charge the specified amount of EVM gas. - /// This is used for basic opcodes (e.g arithmetic, bitwise, ...) that don't have a dedicated - /// benchmark - pub fn charge_evm_gas(&mut self, gas: u64) -> Result<(), DispatchError> { - let base_cost = T::WeightInfo::evm_opcode(1).saturating_sub(T::WeightInfo::evm_opcode(0)); - self.gas_left = self - .gas_left - .checked_sub(&base_cost.saturating_mul(gas)) - .ok_or_else(|| Error::::OutOfGas)?; - Ok(()) + /// Charge the specified token amount of gas or halt if not enough gas is left. + pub fn charge_or_halt>( + &mut self, + token: Tok, + ) -> ControlFlow { + self.charge(token) + .map_or_else(|_| ControlFlow::Break(Error::::OutOfGas.into()), ControlFlow::Continue) } /// Adjust a previously charged amount down to its actual amount. diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 518460c75714d..159a5295a3765 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -73,6 +73,12 @@ pub const PAGE_SIZE: u32 = 4 * 1024; /// Which should always be enough because Solidity allows for 16 local (stack) variables. pub const IMMUTABLE_BYTES: u32 = 4 * 1024; +/// upperbound of memory that can be used by the EVM interpreter. +pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024; + +/// EVM interpreter stack limit. +pub const EVM_STACK_LIMIT: u32 = 1024; + /// Limits that are only enforced on code upload. /// /// # Note @@ -254,7 +260,14 @@ const fn memory_required() -> u32 { let max_call_depth = CALL_STACK_DEPTH + 1; let per_stack_memory = code::PURGABLE_MEMORY_LIMIT + TRANSIENT_STORAGE_BYTES * 2; - let per_frame_memory = code::BASELINE_MEMORY_LIMIT + CALLDATA_BYTES * 2; + + let evm_max_initcode_size = revm::primitives::eip3860::MAX_INITCODE_SIZE as u32; + let evm_overhead = EVM_MEMORY_BYTES + evm_max_initcode_size + EVM_STACK_LIMIT * 32; + let per_frame_memory = if code::BASELINE_MEMORY_LIMIT > evm_overhead { + code::BASELINE_MEMORY_LIMIT + } else { + evm_overhead + } + CALLDATA_BYTES * 2; per_stack_memory + max_call_depth * per_frame_memory } diff --git a/substrate/frame/revive/src/precompiles/builtin/bn128.rs b/substrate/frame/revive/src/precompiles/builtin/bn128.rs index 5742411c2d689..1aad0fbedc40f 100644 --- a/substrate/frame/revive/src/precompiles/builtin/bn128.rs +++ b/substrate/frame/revive/src/precompiles/builtin/bn128.rs @@ -94,7 +94,7 @@ impl PrimitivePrecompile for Bn128Pairing { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - if input.len() % 192 != 0 { + if !input.len().is_multiple_of(192) { Err(DispatchError::from("invalid input length"))?; } diff --git a/substrate/frame/revive/src/precompiles/builtin/modexp.rs b/substrate/frame/revive/src/precompiles/builtin/modexp.rs index 0437e4b32841d..6326980a335f3 100644 --- a/substrate/frame/revive/src/precompiles/builtin/modexp.rs +++ b/substrate/frame/revive/src/precompiles/builtin/modexp.rs @@ -164,7 +164,7 @@ fn calculate_gas_cost( fn calculate_multiplication_complexity(base_length: u64, mod_length: u64) -> u64 { let max_length = max(base_length, mod_length); let mut words = max_length / 8; - if max_length % 8 > 0 { + if !max_length.is_multiple_of(8) { words += 1; } diff --git a/substrate/frame/revive/src/tests/sol/control.rs b/substrate/frame/revive/src/tests/sol/control.rs index dd2359d1f4a7f..042092df500cb 100644 --- a/substrate/frame/revive/src/tests/sol/control.rs +++ b/substrate/frame/revive/src/tests/sol/control.rs @@ -91,7 +91,7 @@ fn jumpdest_works() { builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); let result = builder::bare_call(addr).build().result; - assert_err!(result, Error::::InvalidInstruction); + assert_err!(result, Error::::InvalidJump); }); } diff --git a/substrate/frame/revive/src/tests/sol/memory.rs b/substrate/frame/revive/src/tests/sol/memory.rs index d9df8b645719b..3ebd49dceb30b 100644 --- a/substrate/frame/revive/src/tests/sol/memory.rs +++ b/substrate/frame/revive/src/tests/sol/memory.rs @@ -41,15 +41,13 @@ fn memory_limit_works() { ( "Writing 1 byte from 0 to the limit - 1 should work.", Memory::expandMemoryCall { - memorySize: (crate::limits::code::BASELINE_MEMORY_LIMIT - 1) as u64, + memorySize: (crate::limits::EVM_MEMORY_BYTES - 1) as u64, }, Ok(ExecReturnValue { data: vec![0u8; 32], flags: ReturnFlags::empty() }), ), ( "Writing 1 byte from the limit should revert.", - Memory::expandMemoryCall { - memorySize: crate::limits::code::BASELINE_MEMORY_LIMIT as u64, - }, + Memory::expandMemoryCall { memorySize: crate::limits::EVM_MEMORY_BYTES as u64 }, Err(Error::::OutOfGas.into()), ), ]; diff --git a/substrate/frame/revive/src/tests/sol/tx_info.rs b/substrate/frame/revive/src/tests/sol/tx_info.rs index 86d819d29c746..6a4f0c2b0a616 100644 --- a/substrate/frame/revive/src/tests/sol/tx_info.rs +++ b/substrate/frame/revive/src/tests/sol/tx_info.rs @@ -20,10 +20,8 @@ use crate::{ test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, ExtBuilder, Test}, - vm::evm::U256Converter, Code, Config, Pallet, }; - use alloy_core::sol_types::{SolCall, SolInterface}; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, FixtureType, TransactionInfo}; @@ -48,7 +46,7 @@ fn gasprice_works(fixture_type: FixtureType) { ) .build_and_unwrap_result(); let decoded = TransactionInfo::gaspriceCall::abi_decode_returns(&result.data).unwrap(); - assert_eq!(>::evm_gas_price().into_revm_u256(), decoded); + assert_eq!(>::evm_gas_price().as_u64(), decoded); }); } diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index ac1e913140a4d..04fd659215d32 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -14,39 +14,32 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - use crate::{ - exec::ExecError, - gas, vec, - vm::{BytecodeType, ExecResult, Ext}, - AccountIdOf, Code, CodeInfo, Config, ContractBlob, DispatchError, Error, ExecReturnValue, - RuntimeCosts, H256, LOG_TARGET, U256, -}; -use alloc::{boxed::Box, vec::Vec}; -use core::cmp::min; -use instructions::instruction_table; -use pallet_revive_uapi::ReturnFlags; -use revm::{ - bytecode::Bytecode, - context::CreateScheme, - interpreter::{ - host::DummyHost, - interpreter::{ExtBytecode, ReturnDataImpl, RuntimeFlags}, - interpreter_action::InterpreterAction, - interpreter_types::{InputsTr, MemoryTr, ReturnData}, - CallInput, CallInputs, CallScheme, CreateInputs, FrameInput, Gas, InstructionResult, - Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, - }, - primitives::{self, hardfork::SpecId, Address, Bytes}, + precompiles::Token, + vm::{evm::instructions::exec_instruction, BytecodeType, ExecResult, Ext}, + weights::WeightInfo, + AccountIdOf, CodeInfo, Config, ContractBlob, DispatchError, Error, Weight, H256, LOG_TARGET, + U256, }; -use sp_core::H160; -use sp_runtime::Weight; +use alloc::vec::Vec; +use core::{convert::Infallible, ops::ControlFlow}; +use revm::{bytecode::Bytecode, primitives::Bytes}; #[cfg(feature = "runtime-benchmarks")] pub mod instructions; #[cfg(not(feature = "runtime-benchmarks"))] mod instructions; +mod interpreter; +pub use interpreter::{Halt, Interpreter}; + +mod ext_bytecode; +use ext_bytecode::ExtBytecode; + +mod memory; +mod stack; +mod util; + /// Hard-coded value returned by the EVM `DIFFICULTY` opcode. /// /// After Ethereum's Merge (Sept 2022), the `DIFFICULTY` opcode was redefined to return @@ -60,6 +53,17 @@ pub(crate) const DIFFICULTY: u64 = 2500000000000000_u64; /// For `pallet-revive`, this is hardcoded to 0 pub(crate) const BASE_FEE: U256 = U256::zero(); +/// Cost for a single unit of EVM gas. +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub struct EVMGas(u64); + +impl Token for EVMGas { + fn weight(&self) -> Weight { + let base_cost = T::WeightInfo::evm_opcode(1).saturating_sub(T::WeightInfo::evm_opcode(0)); + base_cost.saturating_mul(self.0) + } +} + impl ContractBlob { /// Create a new contract from EVM init code. pub fn from_evm_init_code(code: Vec, owner: AccountIdOf) -> Result { @@ -124,380 +128,16 @@ impl ContractBlob { } /// Calls the EVM interpreter with the provided bytecode and inputs. -pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, inputs: EVMInputs) -> ExecResult { - let mut interpreter: Interpreter> = Interpreter { - gas: Gas::default(), - bytecode: ExtBytecode::new(bytecode), - stack: Stack::new(), - return_data: Default::default(), - memory: SharedMemory::new(), - input: inputs, - runtime_flag: RuntimeFlags { is_static: ext.is_read_only(), spec_id: SpecId::default() }, - extend: ext, - }; - - let table = instruction_table::<'a, E>(); - let result = run(&mut interpreter, &table); - - instruction_result_into_exec_error::(result.result) - .map(Err) - .unwrap_or_else(|| { - Ok(ExecReturnValue { - flags: if result.is_revert() { ReturnFlags::REVERT } else { ReturnFlags::empty() }, - data: result.output.to_vec(), - }) - }) +pub fn call(bytecode: Bytecode, ext: &mut E, input: Vec) -> ExecResult { + let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext); + let ControlFlow::Break(halt) = run_plain(&mut interpreter); + halt.into() } -/// Runs the EVM interpreter -fn run<'a, E: Ext>( - interpreter: &mut Interpreter>, - table: &revm::interpreter::InstructionTable, DummyHost>, -) -> InterpreterResult { - let host = &mut DummyHost {}; +fn run_plain(interpreter: &mut Interpreter) -> ControlFlow { loop { - #[cfg(not(feature = "std"))] - let action = interpreter.run_plain(table, host); - #[cfg(feature = "std")] - let action = run_plain(interpreter, table, host); - match action { - InterpreterAction::Return(result) => { - log::trace!(target: LOG_TARGET, "Evm return {:?}", result); - debug_assert!( - result.gas == Default::default(), - "Interpreter gas state is unused; found: {:?}", - result.gas, - ); - return result; - }, - InterpreterAction::NewFrame(frame_input) => match frame_input { - FrameInput::Call(call_input) => run_call(interpreter, call_input), - FrameInput::Create(create_input) => run_create(interpreter, create_input), - FrameInput::Empty => unreachable!(), - }, - } - } -} - -fn run_call<'a, E: Ext>( - interpreter: &mut Interpreter>, - call_input: Box, -) { - let callee: H160 = if call_input.scheme.is_delegate_call() { - call_input.bytecode_address.0 .0.into() - } else { - call_input.target_address.0 .0.into() - }; - - let input = match &call_input.input { - CallInput::Bytes(bytes) => bytes.to_vec(), - CallInput::SharedBuffer(range) => interpreter.memory.global_slice(range.clone()).to_vec(), - }; - let call_result = match call_input.scheme { - CallScheme::Call | CallScheme::StaticCall => interpreter.extend.call( - Weight::from_parts(call_input.gas_limit, u64::MAX), - U256::MAX, - &callee, - U256::from_revm_u256(&call_input.call_value()), - input, - true, - call_input.is_static, - ), - CallScheme::CallCode => { - unreachable!() - }, - CallScheme::DelegateCall => interpreter.extend.delegate_call( - Weight::from_parts(call_input.gas_limit, u64::MAX), - U256::MAX, - callee, - input, - ), - }; - - let (return_data, did_revert) = { - let return_value = interpreter.extend.last_frame_output(); - let return_data: Bytes = return_value.data.clone().into(); - (return_data, return_value.did_revert()) - }; - - let mem_length = call_input.return_memory_offset.len(); - let mem_start = call_input.return_memory_offset.start; - let returned_len = return_data.len(); - let target_len = min(mem_length, returned_len); - - interpreter.return_data.set_buffer(return_data); - - match call_result { - Ok(()) => { - // success or revert - gas!(interpreter, RuntimeCosts::CopyToContract(target_len as u32)); - interpreter - .memory - .set(mem_start, &interpreter.return_data.buffer()[..target_len]); - let _ = interpreter.stack.push(primitives::U256::from(!did_revert as u8)); - }, - Err(err) => { - let _ = interpreter.stack.push(primitives::U256::ZERO); - if let Some(reason) = exec_error_into_halt_reason::(err) { - interpreter.halt(reason); - } - }, - } -} - -/// Re-implementation of REVM run_plain function to add trace logging to our EVM interpreter loop. -/// NB: copied directly from revm tag v82 -#[cfg(feature = "std")] -fn run_plain( - interpreter: &mut Interpreter, - instruction_table: &revm::interpreter::InstructionTable, - host: &mut DummyHost, -) -> InterpreterAction { - use crate::{alloc::string::ToString, format}; - use revm::{ - bytecode::OpCode, - interpreter::{ - instruction_context::InstructionContext, - interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr}, - }, - }; - while interpreter.bytecode.is_not_end() { - log::trace!(target: LOG_TARGET, - "[{pc}]: {opcode}, stacktop: {stacktop}, memory size: {memsize} {memory:?}", - pc = interpreter.bytecode.pc(), - opcode = OpCode::new(interpreter.bytecode.opcode()) - .map_or("INVALID".to_string(), |x| format!("{:?}", x.info())), - stacktop = interpreter.stack.top().map_or("None".to_string(), |x| format!("{:#x}", x)), - memsize = interpreter.memory.size(), - // printing at most the first 32 bytes of memory - memory = interpreter - .memory - .slice_len(0, core::cmp::min(32, interpreter.memory.size())) - .to_vec(), - ); - // Get current opcode. let opcode = interpreter.bytecode.opcode(); - - // 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 interpreter.bytecode.relative_jump(1); - let context = InstructionContext { interpreter, host }; - // Execute instruction. - instruction_table[opcode as usize](context); - } - interpreter.bytecode.revert_to_previous_pointer(); - - interpreter.take_next_action() -} - -fn run_create<'a, E: Ext>( - interpreter: &mut Interpreter>, - create_input: Box, -) { - let value = U256::from_revm_u256(&create_input.value); - - let salt = match create_input.scheme { - CreateScheme::Create => None, - CreateScheme::Create2 { salt } => Some(salt.to_be_bytes()), - CreateScheme::Custom { .. } => unreachable!("custom create schemes are not supported"), - }; - - let call_result = interpreter.extend.instantiate( - Weight::from_parts(create_input.gas_limit, u64::MAX), - U256::MAX, - Code::Upload(create_input.init_code.to_vec()), - value, - vec![], - salt.as_ref(), - ); - - let return_value = interpreter.extend.last_frame_output(); - let return_data: Bytes = return_value.data.clone().into(); - - match call_result { - Ok(address) => { - if return_value.did_revert() { - // Contract creation reverted — return data must be propagated - gas!(interpreter, RuntimeCosts::CopyToContract(return_data.len() as u32)); - interpreter.return_data.set_buffer(return_data); - let _ = interpreter.stack.push(primitives::U256::ZERO); - } else { - // Otherwise clear it. Note that RETURN opcode should abort. - interpreter.return_data.clear(); - let stack_item: Address = address.0.into(); - let _ = interpreter.stack.push(stack_item.into_word().into()); - } - }, - Err(err) => { - let _ = interpreter.stack.push(primitives::U256::ZERO); - if let Some(reason) = exec_error_into_halt_reason::(err) { - interpreter.halt(reason); - } - }, - } -} - -/// EVMInterpreter implements the `InterpreterTypes`. -/// -/// Note: -/// -/// Our implementation set the `InterpreterTypes::Extend` associated type, to the `Ext` trait, to -/// reuse all the host functions that are defined by this trait -pub struct EVMInterpreter<'a, E: Ext> { - _phantom: core::marker::PhantomData<&'a E>, -} - -impl<'a, E: Ext> InterpreterTypes for EVMInterpreter<'a, E> { - type Stack = Stack; - type Memory = SharedMemory; - type Bytecode = ExtBytecode; - type ReturnData = ReturnDataImpl; - type Input = EVMInputs; - type RuntimeFlag = RuntimeFlags; - type Extend = &'a mut E; - type Output = InterpreterAction; -} - -/// EVMInputs implements the `InputsTr` trait for EVM inputs, allowing the EVM interpreter to access -/// the call input data. -/// -/// Note: -/// -/// In our implementation of the instruction table, Everything except the call input data will be -/// accessed through the `InterpreterTypes::Extend` associated type, our implementation will panic -/// if any of those methods are called. -#[derive(Debug, Clone, Default)] -pub struct EVMInputs(CallInput); - -impl EVMInputs { - pub fn new(input: Vec) -> Self { - Self(CallInput::Bytes(input.into())) - } -} - -impl InputsTr for EVMInputs { - fn target_address(&self) -> Address { - panic!() - } - - fn caller_address(&self) -> Address { - panic!() - } - - fn bytecode_address(&self) -> Option<&Address> { - panic!() - } - - fn input(&self) -> &CallInput { - &self.0 - } - - fn call_value(&self) -> primitives::U256 { - panic!() - } -} - -/// Conversion of a `ExecError` to `ReturnErrorCode`. -/// -/// Used when converting the error returned from a subcall in order to map it to the -/// equivalent EVM interpreter [InstructionResult]. -/// -/// - Returns `None` when the caller can recover the error. -/// - Otherwise, some [InstructionResult] error code (the halt reason) is returned. Most [ExecError] -/// variants don't map to a [InstructionResult]. The conversion is lossy and defaults to -/// [InstructionResult::Revert] for most cases. -/// -/// Uses the overarching [super::exec_error_into_return_code] method to determine if -/// the error is recoverable or not. This guarantees consistent behavior accross both -/// VM backends. -fn exec_error_into_halt_reason(from: ExecError) -> Option { - log::trace!("call frame execution error in EVM caller: {:?}", &from); - - if super::exec_error_into_return_code::(from).is_ok() { - return None; - } - - let static_memory_too_large = Error::::StaticMemoryTooLarge.into(); - let code_rejected = Error::::CodeRejected.into(); - let transfer_failed = Error::::TransferFailed.into(); - let duplicate_contract = Error::::DuplicateContract.into(); - let balance_conversion_failed = Error::::BalanceConversionFailed.into(); - let value_too_large = Error::::ValueTooLarge.into(); - let out_of_gas = Error::::OutOfGas.into(); - let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); - - Some(match from.error { - err if err == static_memory_too_large => InstructionResult::MemoryLimitOOG, - err if err == code_rejected => InstructionResult::OpcodeNotFound, - err if err == transfer_failed => InstructionResult::OutOfFunds, - err if err == duplicate_contract => InstructionResult::CreateCollision, - err if err == balance_conversion_failed => InstructionResult::OverflowPayment, - err if err == value_too_large => InstructionResult::OverflowPayment, - err if err == out_of_deposit => InstructionResult::OutOfFunds, - err if err == out_of_gas => InstructionResult::OutOfGas, - _ => InstructionResult::Revert, - }) -} - -/// Map [InstructionResult] into an [ExecError] for passing it up the stack. -/// -/// Returns `None` if the instruction result is not an error case. -fn instruction_result_into_exec_error(from: InstructionResult) -> Option { - match from { - InstructionResult::OutOfGas | - InstructionResult::InvalidOperandOOG | - InstructionResult::ReentrancySentryOOG | - InstructionResult::PrecompileOOG | - InstructionResult::MemoryOOG => Some(Error::::OutOfGas), - InstructionResult::MemoryLimitOOG => Some(Error::::StaticMemoryTooLarge), - InstructionResult::OpcodeNotFound | - InstructionResult::InvalidJump | - InstructionResult::NotActivated | - InstructionResult::InvalidFEOpcode | - InstructionResult::CreateContractStartingWithEF => Some(Error::::InvalidInstruction), - InstructionResult::CallNotAllowedInsideStatic | - InstructionResult::StateChangeDuringStaticCall => Some(Error::::StateChangeDenied), - InstructionResult::StackUnderflow | - InstructionResult::StackOverflow | - InstructionResult::NonceOverflow | - InstructionResult::PrecompileError | - InstructionResult::FatalExternalError => Some(Error::::ContractTrapped), - InstructionResult::OutOfOffset => Some(Error::::OutOfBounds), - InstructionResult::CreateCollision => Some(Error::::DuplicateContract), - InstructionResult::OverflowPayment => Some(Error::::BalanceConversionFailed), - InstructionResult::CreateContractSizeLimit | InstructionResult::CreateInitCodeSizeLimit => - Some(Error::::StaticMemoryTooLarge), - InstructionResult::CallTooDeep => Some(Error::::MaxCallDepthReached), - InstructionResult::OutOfFunds => Some(Error::::TransferFailed), - InstructionResult::CreateInitCodeStartingEF00 | - InstructionResult::InvalidEOFInitCode | - InstructionResult::InvalidExtDelegateCallTarget => Some(Error::::ContractTrapped), - InstructionResult::Stop | - InstructionResult::Return | - InstructionResult::Revert | - InstructionResult::SelfDestruct => None, - } - .map(Into::into) -} - -/// Blanket conversion trait between `sp_core::U256` and `revm::primitives::U256` -pub trait U256Converter { - /// Convert `self` into `revm::primitives::U256` - fn into_revm_u256(&self) -> revm::primitives::U256; - - /// Convert from `revm::primitives::U256` into `Self` - fn from_revm_u256(value: &revm::primitives::U256) -> Self; -} - -impl U256Converter for sp_core::U256 { - fn into_revm_u256(&self) -> revm::primitives::U256 { - let bytes = self.to_big_endian(); - revm::primitives::U256::from_be_bytes(bytes) - } - - fn from_revm_u256(value: &revm::primitives::U256) -> Self { - let bytes = value.to_be_bytes::<32>(); - sp_core::U256::from_big_endian(&bytes) + exec_instruction(interpreter, opcode)?; } } diff --git a/substrate/frame/revive/src/vm/evm/ext_bytecode.rs b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs new file mode 100644 index 0000000000000..c72b58194c6dd --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/ext_bytecode.rs @@ -0,0 +1,94 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use core::ops::Deref; +use revm::bytecode::Bytecode; + +/// Extended bytecode structure that wraps base bytecode with additional execution metadata. +#[derive(Debug)] +pub struct ExtBytecode { + /// The base bytecode. + base: Bytecode, + /// The current instruction pointer. + instruction_pointer: *const u8, +} + +impl Deref for ExtBytecode { + type Target = Bytecode; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl Default for ExtBytecode { + fn default() -> Self { + Self::new(Bytecode::default()) + } +} + +impl ExtBytecode { + /// Create new extended bytecode and set the instruction pointer to the start of the bytecode. + pub fn new(base: Bytecode) -> Self { + let instruction_pointer = base.bytecode_ptr(); + Self { base, instruction_pointer } + } + + pub fn bytecode_slice(&self) -> &[u8] { + self.base.original_byte_slice() + } + + /// Relative jumps does not require checking for overflow. + pub fn relative_jump(&mut self, offset: isize) { + // SAFETY: The offset is validated by the caller to ensure it points within the bytecode + self.instruction_pointer = unsafe { self.instruction_pointer.offset(offset) }; + } + + /// Absolute jumps require checking for overflow and if target is a jump destination + /// from jump table. + pub fn absolute_jump(&mut self, offset: usize) { + // SAFETY: The offset is validated by the caller to ensure it points within the bytecode + self.instruction_pointer = unsafe { self.base.bytes_ref().as_ptr().add(offset) }; + } + + /// Check legacy jump destination from jump table. + pub fn is_valid_legacy_jump(&mut self, offset: usize) -> bool { + self.base.legacy_jump_table().expect("Panic if not legacy").is_valid(offset) + } + + /// Returns current program counter. + pub fn pc(&self) -> usize { + // SAFETY: `instruction_pointer` should be at an offset from the start of the bytes. + // In practice this is always true unless a caller modifies the `instruction_pointer` field + // manually. + unsafe { self.instruction_pointer.offset_from_unsigned(self.base.bytes_ref().as_ptr()) } + } + + /// Returns instruction opcode. + pub fn opcode(&self) -> u8 { + // SAFETY: `instruction_pointer` always point to bytecode. + unsafe { *self.instruction_pointer } + } + + /// Reads next `len` bytes from the bytecode. + /// + /// Used by PUSH opcode. + pub fn read_slice(&self, len: usize) -> &[u8] { + // SAFETY: The caller ensures that `len` bytes are available from the current instruction + // pointer position. + unsafe { core::slice::from_raw_parts(self.instruction_pointer, len) } + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index ffd2d968981cf..f8f82f2e0638e 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -15,102 +15,115 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{ - i256::{i256_div, i256_mod}, - Context, -}; -use crate::vm::Ext; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{RuntimeFlag, StackTr}, +pub mod i256; +use i256::{i256_div, i256_mod}; +mod modular; +use modular::Modular; + +use crate::{ + vm::{ + evm::{interpreter::Halt, EVMGas, Interpreter}, + Ext, }, - primitives::U256, + Error, U256, }; +use core::ops::ControlFlow; +use revm::interpreter::gas::{EXP, LOW, MID, VERYLOW}; /// Implements the ADD instruction - adds two values from stack. -pub fn add<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - *op2 = op1.wrapping_add(*op2); +pub fn add(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = op1.overflowing_add(*op2).0; + ControlFlow::Continue(()) } /// Implements the MUL instruction - multiplies two values from stack. -pub fn mul<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); - *op2 = op1.wrapping_mul(*op2); +pub fn mul(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(LOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = op1.overflowing_mul(*op2).0; + ControlFlow::Continue(()) } /// Implements the SUB instruction - subtracts two values from stack. -pub fn sub<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - *op2 = op1.wrapping_sub(*op2); +pub fn sub(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = op1.overflowing_sub(*op2).0; + ControlFlow::Continue(()) } /// Implements the DIV instruction - divides two values from stack. -pub fn div<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); +pub fn div(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(LOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; if !op2.is_zero() { - *op2 = op1.wrapping_div(*op2); + *op2 = op1 / *op2; } + ControlFlow::Continue(()) } /// Implements the SDIV instruction. /// /// Performs signed division of two values from stack. -pub fn sdiv<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); +pub fn sdiv(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(LOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = i256_div(op1, *op2); + ControlFlow::Continue(()) } - /// Implements the MOD instruction. /// /// Pops two values from stack and pushes the remainder of their division. -pub fn rem<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); +pub fn rem(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(LOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; if !op2.is_zero() { - *op2 = op1.wrapping_rem(*op2); + *op2 = op1 % *op2; } + ControlFlow::Continue(()) } /// Implements the SMOD instruction. /// /// Performs signed modulo of two values from stack. -pub fn smod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([op1], op2, context.interpreter); - *op2 = i256_mod(op1, *op2) +pub fn smod(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(LOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = i256_mod(op1, *op2); + ControlFlow::Continue(()) } /// Implements the ADDMOD instruction. /// /// Pops three values from stack and pushes (a + b) % n. -pub fn addmod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::MID); - popn_top!([op1, op2], op3, context.interpreter); - *op3 = op1.add_mod(op2, *op3) +pub fn addmod(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(MID))?; + let ([op1, op2], op3) = interpreter.stack.popn_top()?; + *op3 = op1.add_mod(op2, *op3); + ControlFlow::Continue(()) } /// Implements the MULMOD instruction. /// /// Pops three values from stack and pushes (a * b) % n. -pub fn mulmod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::MID); - popn_top!([op1, op2], op3, context.interpreter); - *op3 = op1.mul_mod(op2, *op3) +pub fn mulmod(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(MID))?; + let ([op1, op2], op3) = interpreter.stack.popn_top()?; + *op3 = op1.mul_mod(op2, *op3); + ControlFlow::Continue(()) } /// Implements the EXP instruction - exponentiates two values from stack. -pub fn exp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - let spec_id = context.interpreter.runtime_flag.spec_id(); - popn_top!([op1], op2, context.interpreter); - gas_or_fail_legacy!(context.interpreter, revm_gas::exp_cost(spec_id, *op2)); +pub fn exp(interpreter: &mut Interpreter) -> ControlFlow { + let ([op1], op2) = interpreter.stack.popn_top()?; + let Some(gas_cost) = exp_cost(*op2) else { + return ControlFlow::Break(Error::::OutOfGas.into()); + }; + interpreter.ext.charge_or_halt(EVMGas(gas_cost))?; *op2 = op1.pow(*op2); + ControlFlow::Continue(()) } /// Implements the `SIGNEXTEND` opcode as defined in the Ethereum Yellow Paper. @@ -142,15 +155,52 @@ pub fn exp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Similarly, if `b == 0` then the yellow paper says the output should start with all zeros, /// then end with bits from `b`; this is equal to `y & mask` where `&` is bitwise `AND`. -pub fn signextend<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::LOW); - popn_top!([ext], x, context.interpreter); +pub fn signextend(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(LOW))?; + let ([ext], x) = interpreter.stack.popn_top()?; // For 31 we also don't need to do anything. if ext < U256::from(31) { - let ext = ext.as_limbs()[0]; + let ext = &ext.0[0]; let bit_index = (8 * ext + 7) as usize; let bit = x.bit(bit_index); let mask = (U256::from(1) << bit_index) - U256::from(1); *x = if bit { *x | !mask } else { *x & mask }; } + ControlFlow::Continue(()) +} + +/// `EXP` opcode cost calculation. +fn exp_cost(power: U256) -> Option { + if power.is_zero() { + Some(EXP) + } else { + // EIP-160: EXP cost increase + let gas_byte = U256::from(50); + let gas = U256::from(EXP) + .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?; + + u64::try_from(gas).ok() + } +} + +const fn log2floor(value: U256) -> u64 { + let mut l: u64 = 256; + let mut i = 3; + loop { + if value.0[i] == 0u64 { + l -= 64; + } else { + l -= value.0[i].leading_zeros() as u64; + if l == 0 { + return l; + } else { + return l - 1; + } + } + if i == 0 { + break; + } + i -= 1; + } + l } diff --git a/substrate/frame/revive/src/vm/evm/instructions/i256.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs similarity index 50% rename from substrate/frame/revive/src/vm/evm/instructions/i256.rs rename to substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs index 44f1b35a101b8..5cf1e38ba5ac2 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/i256.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/i256.rs @@ -16,7 +16,7 @@ // limitations under the License. use core::cmp::Ordering; -use revm::primitives::U256; +use sp_core::U256; /// Represents the sign of a 256-bit signed integer value. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -32,29 +32,16 @@ pub enum Sign { Plus = 1, } -#[cfg(test)] -/// The maximum positive value for a 256-bit signed integer. -pub const MAX_POSITIVE_VALUE: U256 = U256::from_limbs([ - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - 0x7fffffffffffffff, -]); - /// The minimum negative value for a 256-bit signed integer. -pub const MIN_NEGATIVE_VALUE: U256 = U256::from_limbs([ - 0x0000000000000000, - 0x0000000000000000, - 0x0000000000000000, - 0x8000000000000000, -]); +pub const MIN_NEGATIVE_VALUE: U256 = + U256([0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x8000000000000000]); const FLIPH_BITMASK_U64: u64 = 0x7FFF_FFFF_FFFF_FFFF; /// Determines the sign of a 256-bit signed integer. #[inline] pub fn i256_sign(val: &U256) -> Sign { - if val.bit(U256::BITS - 1) { + if val.bit(255) { Sign::Minus } else { // SAFETY: false == 0 == Zero, true == 1 == Plus @@ -74,10 +61,9 @@ pub fn i256_sign_compl(val: &mut U256) -> Sign { #[inline] fn u256_remove_sign(val: &mut U256) { - // SAFETY: U256 does not have any padding bytes - unsafe { - val.as_limbs_mut()[3] &= FLIPH_BITMASK_U64; - } + // Clear the sign bit by masking the highest bit + let limbs = val.0; + *val = U256([limbs[0], limbs[1], limbs[2], limbs[3] & FLIPH_BITMASK_U64]); } /// Computes the two's complement of a U256 value in place. @@ -89,7 +75,7 @@ pub fn two_compl_mut(op: &mut U256) { /// Computes the two's complement of a U256 value. #[inline] pub fn two_compl(op: U256) -> U256 { - op.wrapping_neg() + (!op).overflowing_add(U256::from(1)).0 } /// Compares two 256-bit signed integers. @@ -110,7 +96,7 @@ pub fn i256_cmp(first: &U256, second: &U256) -> Ordering { 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; + return U256::zero(); } let first_sign = i256_sign_compl(&mut first); @@ -140,12 +126,12 @@ pub fn i256_div(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; + return U256::zero(); } let second_sign = i256_sign_compl(&mut second); if second_sign == Sign::Zero { - return U256::ZERO; + return U256::zero(); } let mut r = first % second; @@ -163,111 +149,79 @@ pub fn i256_mod(mut first: U256, mut second: U256) -> U256 { #[cfg(test)] mod tests { use super::*; - use core::num::Wrapping; - use revm::primitives::uint; - - #[test] - fn div_i256() { - // Sanity checks based on i8. Notice that we need to use `Wrapping` here because - // Rust will prevent the overflow by default whereas the EVM does not. - assert_eq!(Wrapping(i8::MIN) / Wrapping(-1), Wrapping(i8::MIN)); - assert_eq!(i8::MAX / -1, -i8::MAX); + use alloy_core::primitives; + use proptest::proptest; - uint! { - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, -1_U256), MIN_NEGATIVE_VALUE); - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, 1_U256), MIN_NEGATIVE_VALUE); - assert_eq!(i256_div(MAX_POSITIVE_VALUE, 1_U256), MAX_POSITIVE_VALUE); - assert_eq!(i256_div(MAX_POSITIVE_VALUE, -1_U256), -1_U256 * MAX_POSITIVE_VALUE); - assert_eq!(i256_div(100_U256, -1_U256), -100_U256); - assert_eq!(i256_div(100_U256, 2_U256), 50_U256); - } + fn alloy_u256(limbs: [u64; 4]) -> primitives::U256 { + primitives::U256::from_limbs(limbs) } + #[test] fn test_i256_sign() { - uint! { - assert_eq!(i256_sign(&0_U256), Sign::Zero); - assert_eq!(i256_sign(&1_U256), Sign::Plus); - assert_eq!(i256_sign(&-1_U256), Sign::Minus); - assert_eq!(i256_sign(&MIN_NEGATIVE_VALUE), Sign::Minus); - assert_eq!(i256_sign(&MAX_POSITIVE_VALUE), Sign::Plus); - } + proptest!(|(a: [u64; 4])| { + let ours = i256_sign(&U256(a)); + let theirs = revm::interpreter::instructions::i256::i256_sign(&alloy_u256(a)); + assert_eq!(ours as i8, theirs as i8); + }); } #[test] fn test_i256_sign_compl() { - uint! { - let mut zero = 0_U256; - let mut positive = 1_U256; - let mut negative = -1_U256; - assert_eq!(i256_sign_compl(&mut zero), Sign::Zero); - assert_eq!(i256_sign_compl(&mut positive), Sign::Plus); - assert_eq!(i256_sign_compl(&mut negative), Sign::Minus); - } + proptest!(|(a: [u64; 4])| { + let mut ours_in = U256(a); + let ours = i256_sign_compl(&mut ours_in); + let mut theirs_in = alloy_u256(a); + let theirs = revm::interpreter::instructions::i256::i256_sign_compl(&mut theirs_in); + assert_eq!(&ours_in.0, theirs_in.as_limbs()); + assert_eq!(ours as u8, theirs as u8); + }); } #[test] fn test_two_compl() { - uint! { - assert_eq!(two_compl(0_U256), 0_U256); - assert_eq!(two_compl(1_U256), -1_U256); - assert_eq!(two_compl(-1_U256), 1_U256); - assert_eq!(two_compl(2_U256), -2_U256); - assert_eq!(two_compl(-2_U256), 2_U256); - - // Two's complement of the min value is itself. - assert_eq!(two_compl(MIN_NEGATIVE_VALUE), MIN_NEGATIVE_VALUE); - } + proptest!(|(a: [u64; 4])| { + let ours = two_compl(U256(a)); + let theirs = revm::interpreter::instructions::i256::two_compl(alloy_u256(a)); + assert_eq!(&ours.0, theirs.as_limbs()); + }); } #[test] fn test_two_compl_mut() { - uint! { - let mut value = 1_U256; - two_compl_mut(&mut value); - assert_eq!(value, -1_U256); - } + proptest!(|(limbs: [u64; 4])| { + let mut ours = U256(limbs); + two_compl_mut(&mut ours); + let theirs_value = alloy_core::primitives::U256::from_limbs(limbs); + let theirs = theirs_value.wrapping_neg(); + assert_eq!(&ours.0, theirs.as_limbs()); + }); } - #[test] fn test_i256_cmp() { - uint! { - assert_eq!(i256_cmp(&1_U256, &2_U256), Ordering::Less); - assert_eq!(i256_cmp(&2_U256, &2_U256), Ordering::Equal); - assert_eq!(i256_cmp(&3_U256, &2_U256), Ordering::Greater); - assert_eq!(i256_cmp(&-1_U256, &-1_U256), Ordering::Equal); - assert_eq!(i256_cmp(&-1_U256, &-2_U256), Ordering::Greater); - assert_eq!(i256_cmp(&-1_U256, &0_U256), Ordering::Less); - assert_eq!(i256_cmp(&-2_U256, &2_U256), Ordering::Less); - } + proptest!(|(a: [u64; 4], b: [u64; 4])| { + let first = U256(a); + let second = U256(b); + let ours = i256_cmp(&first, &second); + let theirs = revm::interpreter::instructions::i256::i256_cmp(&alloy_u256(a), &alloy_u256(b)); + assert_eq!(ours, theirs); + }); } #[test] fn test_i256_div() { - uint! { - assert_eq!(i256_div(1_U256, 0_U256), 0_U256); - assert_eq!(i256_div(0_U256, 1_U256), 0_U256); - assert_eq!(i256_div(0_U256, -1_U256), 0_U256); - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, 1_U256), MIN_NEGATIVE_VALUE); - assert_eq!(i256_div(4_U256, 2_U256), 2_U256); - assert_eq!(i256_div(MIN_NEGATIVE_VALUE, MIN_NEGATIVE_VALUE), 1_U256); - assert_eq!(i256_div(2_U256, -1_U256), -2_U256); - assert_eq!(i256_div(-2_U256, -1_U256), 2_U256); - } + proptest!(|(a: [u64; 4], b: [u64; 4])| { + let ours = i256_div(U256(a), U256(b)); + let theirs = revm::interpreter::instructions::i256::i256_div(alloy_u256(a), alloy_u256(b)); + assert_eq!(&ours.0, theirs.as_limbs()); + }); } #[test] fn test_i256_mod() { - uint! { - assert_eq!(i256_mod(0_U256, 1_U256), 0_U256); - assert_eq!(i256_mod(1_U256, 0_U256), 0_U256); - assert_eq!(i256_mod(4_U256, 2_U256), 0_U256); - assert_eq!(i256_mod(3_U256, 2_U256), 1_U256); - assert_eq!(i256_mod(MIN_NEGATIVE_VALUE, 1_U256), 0_U256); - assert_eq!(i256_mod(2_U256, 2_U256), 0_U256); - assert_eq!(i256_mod(2_U256, 3_U256), 2_U256); - assert_eq!(i256_mod(-2_U256, 3_U256), -2_U256); - assert_eq!(i256_mod(2_U256, -3_U256), 2_U256); - assert_eq!(i256_mod(-2_U256, -3_U256), -2_U256); - } + proptest!(|(a: [u64; 4], b: [u64; 4])| { + let ours = i256_mod(U256(a), U256(b)); + let theirs = revm::interpreter::instructions::i256::i256_mod(alloy_u256(a), alloy_u256(b)); + assert_eq!(&ours.0, theirs.as_limbs()); + }); } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs new file mode 100644 index 0000000000000..165f051218461 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic/modular.rs @@ -0,0 +1,133 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use alloy_core::primitives::ruint::algorithms; +use sp_core::U256; + +pub trait Modular { + /// Compute $\mod{\mathtt{self}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + // FEATURE: Reduce larger bit-sizes to smaller ones. + #[must_use] + fn reduce_mod(self, modulus: Self) -> Self; + + /// Compute $\mod{\mathtt{self} + \mathtt{rhs}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + #[must_use] + fn add_mod(self, rhs: Self, modulus: Self) -> Self; + + /// Compute $\mod{\mathtt{self} ⋅ \mathtt{rhs}}_{\mathtt{modulus}}$. + /// + /// Returns zero if the modulus is zero. + /// + /// See [`mul_redc`](Self::mul_redc) for a faster variant at the cost of + /// some pre-computation. + #[must_use] + fn mul_mod(self, rhs: Self, modulus: Self) -> Self; +} + +impl Modular for U256 { + fn reduce_mod(mut self, modulus: Self) -> Self { + if modulus.is_zero() { + return Self::zero(); + } + if self >= modulus { + self %= modulus; + } + self + } + + fn add_mod(self, rhs: Self, modulus: Self) -> Self { + if modulus.is_zero() { + return Self::zero(); + } + // Reduce inputs + let lhs = self.reduce_mod(modulus); + let rhs = rhs.reduce_mod(modulus); + + // Compute the sum and conditionally subtract modulus once. + let (mut result, overflow) = lhs.overflowing_add(rhs); + if overflow || result >= modulus { + result = result.overflowing_sub(modulus).0; + } + result + } + + fn mul_mod(self, rhs: Self, mut modulus: Self) -> Self { + if modulus.is_zero() { + return Self::zero(); + } + + // Allocate at least `nlimbs(2 * BITS)` limbs to store the product. This array + // casting is a workaround for `generic_const_exprs` not being stable. + let mut product = [[0u64; 2]; 4]; + let product_len = 8; + // SAFETY: `[[u64; 2]; 4] = [u64; 8]`. + let product = unsafe { + core::slice::from_raw_parts_mut(product.as_mut_ptr().cast::(), product_len) + }; + + // Compute full product. + let overflow = algorithms::addmul(product, &self.0, &rhs.0); + debug_assert!(!overflow, "addmul overflowed for 256-bit inputs"); + + // Compute modulus using `div_rem`. + // This stores the remainder in the divisor, `modulus`. + algorithms::div(product, &mut modulus.0); + + modulus + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_core::primitives; + use proptest::proptest; + + fn alloy_u256(limbs: [u64; 4]) -> primitives::U256 { + primitives::U256::from_limbs(limbs) + } + + #[test] + fn test_reduce_mod() { + proptest!(|(a: [u64; 4], m: [u64; 4])| { + let ours = U256(a).reduce_mod(U256(m)); + let theirs = alloy_u256(a).reduce_mod(alloy_u256(m)); + assert_eq!(&ours.0, theirs.as_limbs()); + }); + } + + #[test] + fn test_add_mod() { + proptest!(|(a: [u64; 4], b: [u64; 4], m: [u64; 4])| { + let ours = U256(a).add_mod(U256(b), U256(m)); + let theirs = alloy_u256(a).add_mod(alloy_u256(b), alloy_u256(m)); + assert_eq!(&ours.0, theirs.as_limbs()); + }); + } + + #[test] + fn test_mul_mod() { + proptest!(|(a: [u64; 4], b: [u64; 4], m: [u64; 4])| { + let ours = U256(a).mul_mod(U256(b), U256(m)); + let theirs = alloy_u256(a).mul_mod(alloy_u256(b), alloy_u256(m)); + assert_eq!(&ours.0, theirs.as_limbs()); + }); + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs index 7eea29dca6358..1c334f36d8659 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise.rs @@ -15,464 +15,613 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{i256::i256_cmp, Context}; -use crate::vm::Ext; -use core::cmp::Ordering; -use revm::{ - interpreter::{gas as revm_gas, interpreter_types::StackTr}, - primitives::U256, +mod bits; + +use super::{arithmetic::i256::i256_cmp, utility::as_usize_saturated}; +use crate::{ + vm::{ + evm::{interpreter::Halt, EVMGas, Interpreter}, + Ext, + }, + U256, }; +use bits::Bits; +use core::{cmp::Ordering, ops::ControlFlow}; +use revm::interpreter::gas::VERYLOW; /// Implements the LT instruction - less than comparison. -pub fn lt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - *op2 = U256::from(op1 < *op2); +pub fn lt(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; + *op2 = if op1 < *op2 { U256::one() } else { U256::zero() }; + ControlFlow::Continue(()) } /// Implements the GT instruction - greater than comparison. -pub fn gt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn gt(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(op1 > *op2); + *op2 = if op1 > *op2 { U256::one() } else { U256::zero() }; + ControlFlow::Continue(()) } /// Implements the CLZ instruction - count leading zeros. -pub fn clz<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], op1, context.interpreter); +pub fn clz(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([], op1) = interpreter.stack.popn_top()?; let leading_zeros = op1.leading_zeros(); *op1 = U256::from(leading_zeros); + ControlFlow::Continue(()) } /// Implements the SLT instruction. /// /// Signed less than comparison of two values from stack. -pub fn slt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn slt(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(i256_cmp(&op1, op2) == Ordering::Less); + *op2 = if i256_cmp(&op1, op2) == Ordering::Less { U256::one() } else { U256::zero() }; + ControlFlow::Continue(()) } /// Implements the SGT instruction. /// /// Signed greater than comparison of two values from stack. -pub fn sgt<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn sgt(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(i256_cmp(&op1, op2) == Ordering::Greater); + *op2 = if i256_cmp(&op1, op2) == Ordering::Greater { U256::one() } else { U256::zero() }; + ControlFlow::Continue(()) } /// Implements the EQ instruction. /// /// Equality comparison of two values from stack. -pub fn eq<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn eq(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - *op2 = U256::from(op1 == *op2); + *op2 = if op1 == *op2 { U256::one() } else { U256::zero() }; + ControlFlow::Continue(()) } /// Implements the ISZERO instruction. /// /// Checks if the top stack value is zero. -pub fn iszero<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], op1, context.interpreter); - *op1 = U256::from(op1.is_zero()); +pub fn iszero(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([], op1) = interpreter.stack.popn_top()?; + + *op1 = if op1.is_zero() { U256::one() } else { U256::zero() }; + ControlFlow::Continue(()) } /// Implements the AND instruction. /// /// Bitwise AND of two values from stack. -pub fn bitand<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn bitand(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 & *op2; + ControlFlow::Continue(()) } /// Implements the OR instruction. /// /// Bitwise OR of two values from stack. -pub fn bitor<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - +pub fn bitor(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 | *op2; + ControlFlow::Continue(()) } /// Implements the XOR instruction. /// /// Bitwise XOR of two values from stack. -pub fn bitxor<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); - +pub fn bitxor(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; *op2 = op1 ^ *op2; + ControlFlow::Continue(()) } /// Implements the NOT instruction. /// /// Bitwise NOT (negation) of the top stack value. -pub fn not<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], op1, context.interpreter); - +pub fn not(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([], op1) = interpreter.stack.popn_top()?; *op1 = !*op1; + ControlFlow::Continue(()) } /// Implements the BYTE instruction. /// /// Extracts a single byte from a word at a given index. -pub fn byte<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn byte(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - let o1 = as_usize_saturated!(op1); + let o1 = op1.as_usize(); *op2 = if o1 < 32 { // `31 - o1` because `byte` returns LE, while we want BE U256::from(op2.byte(31 - o1)) } else { - U256::ZERO + U256::zero() }; + ControlFlow::Continue(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn shl<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn shl(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - let shift = as_usize_saturated!(op1); - *op2 = if shift < 256 { *op2 << shift } else { U256::ZERO } + let shift = as_usize_saturated(op1); + *op2 = if shift < 256 { *op2 << shift } else { U256::zero() }; + ControlFlow::Continue(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn shr<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn shr(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - let shift = as_usize_saturated!(op1); - *op2 = if shift < 256 { *op2 >> shift } else { U256::ZERO } + let shift = as_usize_saturated(op1); + *op2 = if shift < 256 { *op2 >> shift } else { U256::zero() }; + ControlFlow::Continue(()) } /// EIP-145: Bitwise shifting instructions in EVM -pub fn sar<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([op1], op2, context.interpreter); +pub fn sar(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([op1], op2) = interpreter.stack.popn_top()?; - let shift = as_usize_saturated!(op1); + let shift = as_usize_saturated(op1); *op2 = if shift < 256 { op2.arithmetic_shr(shift) } else if op2.bit(255) { U256::MAX } else { - U256::ZERO + U256::zero() }; + ControlFlow::Continue(()) } #[cfg(test)] mod tests { use super::{byte, clz, sar, shl, shr}; - use revm::{ - interpreter::{host::DummyHost, InstructionContext}, - primitives::{hardfork::SpecId, uint, U256}, - }; - - pub fn test_interpreter() -> revm::interpreter::Interpreter< - crate::vm::evm::EVMInterpreter<'static, crate::exec::mock_ext::MockExt>, - > { - use crate::tests::Test; - use revm::{ - interpreter::{ - interpreter::{RuntimeFlags, SharedMemory}, - Interpreter, Stack, - }, - primitives::hardfork::SpecId, + use crate::{tests::Test, vm::evm::Interpreter}; + use alloy_core::hex; + use core::ops::ControlFlow; + use sp_core::U256; + + macro_rules! test_interpreter { + ($interpreter: ident) => { + let mut mock_ext = crate::exec::mock_ext::MockExt::::new(); + let mut $interpreter = Interpreter::new(Default::default(), vec![], &mut mock_ext); }; - - let mock_ext = Box::leak(Box::new(crate::exec::mock_ext::MockExt::::new())); - - Interpreter { - gas: revm::interpreter::Gas::new(0), - bytecode: Default::default(), - stack: Stack::new(), - return_data: Default::default(), - memory: SharedMemory::new(), - input: crate::vm::evm::EVMInputs::default(), - runtime_flag: RuntimeFlags { is_static: false, spec_id: SpecId::default() }, - extend: mock_ext, - } } #[test] fn test_shift_left() { - let mut interpreter = test_interpreter(); - struct TestCase { value: U256, shift: U256, expected: U256, } - uint! { - let test_cases = [ - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x00_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000002_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0xff_U256, - expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x0101_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x00_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe_U256, - }, - ]; - } + let test_cases = [ + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000002" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("0101")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + )), + }, + ]; + test_interpreter!(interpreter); for test in test_cases { - push!(interpreter, test.value); - push!(interpreter, test.shift); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - shl(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); + assert!((|| { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + shl(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected); + ControlFlow::Continue(()) + })() + .is_continue()); } } #[test] fn test_logical_shift_right() { - let mut interpreter = test_interpreter(); - struct TestCase { value: U256, shift: U256, expected: U256, } - uint! { - let test_cases = [ - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x00_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0xff_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0101_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x00_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - }, - ]; - } + let test_cases = [ + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "4000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0101")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ]; + test_interpreter!(interpreter); for test in test_cases { - push!(interpreter, test.value); - push!(interpreter, test.shift); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - shr(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); + assert!((|| { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + shr(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected); + ControlFlow::Continue(()) + })() + .is_continue()); } } #[test] fn test_arithmetic_shift_right() { - let mut interpreter = test_interpreter(); - struct TestCase { value: U256, shift: U256, expected: U256, } - uint! { let test_cases = [ TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x00_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), }, TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), }, TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0xc000000000000000000000000000000000000000000000000000000000000000_U256, + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "c000000000000000000000000000000000000000000000000000000000000000" + )), }, TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0xff_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0100_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x0101_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("0101")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x00_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("00")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x01_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), }, TestCase { - value: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0x01_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, + value: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("01")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), }, TestCase { - value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, - shift: 0xfe_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, + value: U256::from_big_endian(&hex!( + "4000000000000000000000000000000000000000000000000000000000000000" + )), + shift: U256::from_big_endian(&hex!("fe")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), }, TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xf8_U256, - expected: 0x000000000000000000000000000000000000000000000000000000000000007f_U256, + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("f8")), + expected: U256::from_big_endian(&hex!( + "000000000000000000000000000000000000000000000000000000000000007f" + )), }, TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xfe_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000001_U256, + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("fe")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + )), }, TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0xff_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("ff")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), }, TestCase { - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - shift: 0x0100_U256, - expected: 0x0000000000000000000000000000000000000000000000000000000000000000_U256, + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + shift: U256::from_big_endian(&hex!("0100")), + expected: U256::from_big_endian(&hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), }, ]; - } + test_interpreter!(interpreter); for test in test_cases { - push!(interpreter, test.value); - push!(interpreter, test.shift); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - sar(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected); + assert!((|| { + interpreter.stack.push(test.value)?; + interpreter.stack.push(test.shift)?; + sar(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected); + ControlFlow::Continue(()) + })() + .is_continue()); } } @@ -484,82 +633,88 @@ mod tests { expected: U256, } - let mut interpreter = test_interpreter(); - - let input_value = U256::from(0x1234567890abcdef1234567890abcdef_u128); + let input_value = U256::from_big_endian(&hex!("1234567890abcdef1234567890abcdef")); let test_cases = (0..32) .map(|i| { let byte_pos = 31 - i; - let shift_amount = U256::from(byte_pos * 8); let byte_value = (input_value >> shift_amount) & U256::from(0xFF); TestCase { input: input_value, index: i, expected: byte_value } }) .collect::>(); + test_interpreter!(interpreter); for test in test_cases.iter() { - push!(interpreter, test.input); - push!(interpreter, U256::from(test.index)); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - byte(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!(res, test.expected, "Failed at index: {}", test.index); + assert!((|| { + interpreter.stack.push(test.input)?; + interpreter.stack.push(U256::from(test.index))?; + byte(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!(res, test.expected, "Failed at index: {}", test.index); + ControlFlow::Continue(()) + })() + .is_continue()); } } #[test] fn test_clz() { - let mut interpreter = test_interpreter(); - interpreter.runtime_flag.spec_id = SpecId::OSAKA; - struct TestCase { value: U256, expected: U256, } - uint! { - let test_cases = [ - TestCase { value: 0x0_U256, expected: 256_U256 }, - TestCase { value: 0x1_U256, expected: 255_U256 }, - TestCase { value: 0x2_U256, expected: 254_U256 }, - TestCase { value: 0x3_U256, expected: 254_U256 }, - TestCase { value: 0x4_U256, expected: 253_U256 }, - TestCase { value: 0x7_U256, expected: 253_U256 }, - TestCase { value: 0x8_U256, expected: 252_U256 }, - TestCase { value: 0xff_U256, expected: 248_U256 }, - TestCase { value: 0x100_U256, expected: 247_U256 }, - TestCase { value: 0xffff_U256, expected: 240_U256 }, - TestCase { - value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, // U256::MAX - expected: 0_U256, - }, - TestCase { - value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 255 - expected: 0_U256, - }, - TestCase { // Smallest value with 1 leading zero - value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 254 - expected: 1_U256, - }, - TestCase { // Value just below 1 << 255 - value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, - expected: 1_U256, - }, - ]; - } + let test_cases = [ + TestCase { value: U256::from_big_endian(&hex!("00")), expected: U256::from(256) }, + TestCase { value: U256::from_big_endian(&hex!("01")), expected: U256::from(255) }, + TestCase { value: U256::from_big_endian(&hex!("02")), expected: U256::from(254) }, + TestCase { value: U256::from_big_endian(&hex!("03")), expected: U256::from(254) }, + TestCase { value: U256::from_big_endian(&hex!("04")), expected: U256::from(253) }, + TestCase { value: U256::from_big_endian(&hex!("07")), expected: U256::from(253) }, + TestCase { value: U256::from_big_endian(&hex!("08")), expected: U256::from(252) }, + TestCase { value: U256::from_big_endian(&hex!("ff")), expected: U256::from(248) }, + TestCase { value: U256::from_big_endian(&hex!("0100")), expected: U256::from(247) }, + TestCase { value: U256::from_big_endian(&hex!("ffff")), expected: U256::from(240) }, + TestCase { + value: U256::from_big_endian(&hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + expected: U256::from(0), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "8000000000000000000000000000000000000000000000000000000000000000" + )), + expected: U256::from(0), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "4000000000000000000000000000000000000000000000000000000000000000" + )), + expected: U256::from(1), + }, + TestCase { + value: U256::from_big_endian(&hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + expected: U256::from(1), + }, + ]; - for test in test_cases { - push!(interpreter, test.value); - let context = - InstructionContext { host: &mut DummyHost, interpreter: &mut interpreter }; - clz(context); - let res = interpreter.stack.pop().unwrap(); - assert_eq!( - res, test.expected, - "CLZ for value {:#x} failed. Expected: {}, Got: {}", - test.value, test.expected, res - ); + test_interpreter!(interpreter); + for test in test_cases.iter() { + assert!((|| { + interpreter.stack.push(test.value)?; + clz(&mut interpreter)?; + let [res] = interpreter.stack.popn::<1>()?; + assert_eq!( + res, test.expected, + "CLZ for value {:#x} failed. Expected: {}, Got: {}", + test.value, test.expected, res + ); + ControlFlow::Continue(()) + })() + .is_continue()); } } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs b/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs new file mode 100644 index 0000000000000..52262daf1ce22 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/instructions/bitwise/bits.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sp_core::U256; + +pub trait Bits { + /// Arithmetic shift right by `rhs` bits. + #[must_use] + fn arithmetic_shr(self, rhs: usize) -> Self; +} + +impl Bits for U256 { + fn arithmetic_shr(self, rhs: usize) -> Self { + const BITS: usize = 256; + let sign = self.bit(BITS - 1); + let mut r = self >> rhs; + if sign { + r |= U256::MAX << BITS.saturating_sub(rhs); + } + r + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::proptest; + + #[test] + fn test_arithmetic_shr() { + proptest!(|(limbs: [u64; 4], shift in 0usize..=258)| { + let ours = U256(limbs).arithmetic_shr(shift); + let theirs = alloy_core::primitives::U256::from_limbs(limbs).arithmetic_shr(shift); + assert_eq!(&ours.0, theirs.as_limbs()); + }); + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs index 0f91828f7e65c..b2d236e22c790 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs @@ -15,78 +15,81 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; use crate::{ vm::{ - evm::{U256Converter, BASE_FEE, DIFFICULTY}, + evm::{interpreter::Halt, EVMGas, Interpreter, BASE_FEE, DIFFICULTY}, Ext, }, - RuntimeCosts, + Error, RuntimeCosts, }; -use revm::{ - interpreter::gas as revm_gas, - primitives::{Address, U256}, -}; -use sp_core::H160; +use core::ops::ControlFlow; +use revm::interpreter::gas::BASE; +use sp_core::{H160, U256}; /// EIP-1344: ChainID opcode -pub fn chainid<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.extend.chain_id())); +pub fn chainid(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + interpreter.stack.push(interpreter.ext.chain_id())?; + ControlFlow::Continue(()) } /// Implements the COINBASE instruction. /// /// Pushes the current block's beneficiary address onto the stack. -pub fn coinbase<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BlockAuthor); - let coinbase: Address = - context.interpreter.extend.block_author().unwrap_or(H160::zero()).0.into(); - push!(context.interpreter, coinbase.into_word().into()); +pub fn coinbase(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::BlockAuthor)?; + let coinbase = interpreter.ext.block_author().unwrap_or(H160::zero()); + interpreter.stack.push(coinbase)?; + ControlFlow::Continue(()) } /// Implements the TIMESTAMP instruction. /// /// Pushes the current block's timestamp onto the stack. -pub fn timestamp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Now); - let timestamp = context.interpreter.extend.now(); - push!(context.interpreter, timestamp.into_revm_u256()); +pub fn timestamp(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::Now)?; + let timestamp = interpreter.ext.now(); + interpreter.stack.push(timestamp)?; + ControlFlow::Continue(()) } /// Implements the NUMBER instruction. /// /// Pushes the current block number onto the stack. -pub fn block_number<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BlockNumber); - let block_number = context.interpreter.extend.block_number(); - push!(context.interpreter, block_number.into_revm_u256()); +pub fn block_number(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::BlockNumber)?; + let block_number = interpreter.ext.block_number(); + interpreter.stack.push(block_number)?; + ControlFlow::Continue(()) } /// Implements the DIFFICULTY/PREVRANDAO instruction. /// /// Pushes the block difficulty (pre-merge) or prevrandao (post-merge) onto the stack. -pub fn difficulty<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(DIFFICULTY)); +pub fn difficulty(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + interpreter.stack.push(U256::from(DIFFICULTY))?; + ControlFlow::Continue(()) } /// Implements the GASLIMIT instruction. /// /// Pushes the current block's gas limit onto the stack. -pub fn gaslimit<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::GasLimit); - let gas_limit = context.interpreter.extend.gas_limit(); - push!(context.interpreter, U256::from(gas_limit)); +pub fn gaslimit(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::GasLimit)?; + let gas_limit = interpreter.ext.gas_limit(); + interpreter.stack.push(U256::from(gas_limit))?; + ControlFlow::Continue(()) } /// EIP-3198: BASEFEE opcode -pub fn basefee<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BaseFee); - push!(context.interpreter, BASE_FEE.into_revm_u256()); +pub fn basefee(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::BaseFee)?; + interpreter.stack.push(BASE_FEE)?; + ControlFlow::Continue(()) } /// EIP-7516: BLOBBASEFEE opcode is not supported -pub fn blob_basefee<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); +pub fn blob_basefee<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Error::::InvalidInstruction.into()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index e96588fd9b0e9..421f592b8462f 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -17,130 +17,119 @@ mod call_helpers; -use super::{utility::IntoAddress, Context}; +use super::utility::IntoAddress; use crate::{ - vm::{evm::U256Converter, Ext, RuntimeCosts}, - Pallet, -}; -use alloc::boxed::Box; -pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges}; -use revm::{ - context_interface::CreateScheme, - interpreter::{ - gas as revm_gas, - interpreter_action::{ - CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, InterpreterAction, - }, - interpreter_types::{LoopControl, RuntimeFlag, StackTr}, - CallInput, InstructionResult, + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt, Interpreter}, + Ext, RuntimeCosts, }, - primitives::{Address, Bytes, B256, U256}, + Code, Error, Pallet, Weight, H160, LOG_TARGET, U256, +}; +use alloc::{vec, vec::Vec}; +pub use call_helpers::{calc_call_gas, get_memory_in_and_out_ranges}; +use core::{ + cmp::min, + ops::{ControlFlow, Range}, }; +use revm::interpreter::interpreter_action::CallScheme; /// Implements the CREATE/CREATE2 instruction. /// /// Creates a new contract with provided bytecode. -pub fn create<'ext, const IS_CREATE2: bool, E: Ext>(context: Context<'_, 'ext, E>) { - if context.interpreter.extend.is_read_only() { - context.interpreter.halt(InstructionResult::Revert); - return; +pub fn create( + interpreter: &mut Interpreter, +) -> ControlFlow { + if interpreter.ext.is_read_only() { + return ControlFlow::Break(Error::::StateChangeDenied.into()); } - popn!([value, code_offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); + let [value, code_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt::(len)?; // TODO: We do not charge for the new code in storage. When implementing the new gas: // Introduce EthInstantiateWithCode, which shall charge gas based on the code length. // See #9577 for more context. - let val = crate::U256::from_revm_u256(&value); - gas!( - context.interpreter, - RuntimeCosts::Instantiate { - input_data_len: len as u32, // We charge for initcode execution - balance_transfer: Pallet::::has_balance(val), - dust_transfer: Pallet::::has_dust(val), - } - ); + interpreter.ext.charge_or_halt(RuntimeCosts::Instantiate { + input_data_len: len as u32, // We charge for initcode execution + balance_transfer: Pallet::::has_balance(value), + dust_transfer: Pallet::::has_dust(value), + })?; - let mut code = Bytes::new(); + let mut code = Vec::new(); if len != 0 { // EIP-3860: Limit initcode if len > revm::primitives::eip3860::MAX_INITCODE_SIZE { - context.interpreter.halt(InstructionResult::CreateInitCodeSizeLimit); - return; + return ControlFlow::Break(Error::::BlobTooLarge.into()); } - let code_offset = as_usize_or_fail!(context.interpreter, code_offset); - resize_memory!(context.interpreter, code_offset, len); - code = - Bytes::copy_from_slice(context.interpreter.memory.slice_len(code_offset, len).as_ref()); + let code_offset = as_usize_or_halt::(code_offset)?; + interpreter.memory.resize(code_offset, len)?; + code = interpreter.memory.slice_len(code_offset, len).to_vec(); } - // EIP-1014: Skinny CREATE2 - let scheme = if IS_CREATE2 { - popn!([salt], context.interpreter); - CreateScheme::Create2 { salt } + let salt = if IS_CREATE2 { + let [salt] = interpreter.stack.popn()?; + Some(salt.to_big_endian()) } else { - gas_legacy!(context.interpreter, revm_gas::CREATE); - CreateScheme::Create + None }; - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Create(Box::new(CreateInputs { - caller: context.interpreter.extend.address().0.into(), - scheme, - value, - init_code: code, - gas_limit: u64::MAX, // TODO: set the right limit - })))); + let call_result = interpreter.ext.instantiate( + Weight::from_parts(u64::MAX, u64::MAX), // TODO: set the right limit + U256::MAX, + Code::Upload(code), + value, + vec![], + salt.as_ref(), + ); + + match call_result { + Ok(address) => { + let return_value = interpreter.ext.last_frame_output(); + if return_value.did_revert() { + // Contract creation reverted — return data must be propagated + interpreter.stack.push(U256::zero()) + } else { + // Otherwise clear it. Note that RETURN opcode should abort. + *interpreter.ext.last_frame_output_mut() = Default::default(); + interpreter.stack.push(address) + } + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Create failed: {err:?}"); + interpreter.stack.push(U256::zero())?; + ControlFlow::Continue(()) + }, + } } /// Implements the CALL instruction. /// /// Message call with value transfer to another account. -pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to, value], context.interpreter); +pub fn call(interpreter: &mut Interpreter) -> ControlFlow { + let [_local_gas_limit, to, value] = interpreter.stack.popn()?; let to = to.into_address(); // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be // addressed in #9577. - let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let has_transfer = !value.is_zero(); - if context.interpreter.runtime_flag.is_static() && has_transfer { - context.interpreter.halt(InstructionResult::CallNotAllowedInsideStatic); - return; + if interpreter.ext.is_read_only() && has_transfer { + return ControlFlow::Break(Error::::StateChangeDenied.into()); } - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; - + let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?; let scheme = CallScheme::Call; - let input = CallInput::SharedBuffer(input); - - let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), value) else { - return; - }; - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input, - gas_limit, - target_address: to, - caller: Address::default(), - bytecode_address: to, - value: CallValue::Transfer(value), - scheme, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - })))); + let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + + run_call( + interpreter, + to, + interpreter.memory.slice(input).to_vec(), + scheme, + Weight::from_parts(gas_limit, u64::MAX), + value, + return_memory_range, + ) } /// Implements the CALLCODE instruction. @@ -149,86 +138,110 @@ pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Isn't supported yet: [`solc` no longer emits it since Solidity v0.3.0 in 2016] /// (https://soliditylang.org/blog/2016/03/11/solidity-0.3.0-release-announcement/). -pub fn call_code<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); +pub fn call_code(_interpreter: &mut Interpreter) -> ControlFlow { + ControlFlow::Break(Error::::InvalidInstruction.into()) } /// Implements the DELEGATECALL instruction. /// /// Message call with alternative account's code but same sender and value. -pub fn delegate_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to], context.interpreter); - let to = Address::from_word(B256::from(to)); +pub fn delegate_call(interpreter: &mut Interpreter) -> ControlFlow { + let [_local_gas_limit, to] = interpreter.stack.popn()?; + let to = to.into_address(); // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be // addressed in #9577. - let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; + let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?; let scheme = CallScheme::DelegateCall; - let input = CallInput::SharedBuffer(input); - - let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), U256::ZERO) - else { - return; - }; - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input, - gas_limit, - target_address: Default::default(), - caller: Default::default(), - bytecode_address: to, - value: CallValue::Apparent(Default::default()), - scheme, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - })))); + let value = U256::zero(); + let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + + run_call( + interpreter, + to, + interpreter.memory.slice(input).to_vec(), + scheme, + Weight::from_parts(gas_limit, u64::MAX), + value, + return_memory_range, + ) } /// Implements the STATICCALL instruction. /// /// Static message call (cannot modify state). -pub fn static_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to], context.interpreter); - let to = Address::from_word(B256::from(to)); +pub fn static_call(interpreter: &mut Interpreter) -> ControlFlow { + let [_local_gas_limit, to] = interpreter.stack.popn()?; + let to = to.into_address(); // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be // addressed in #9577. - let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; - + let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?; let scheme = CallScheme::StaticCall; - let input = CallInput::SharedBuffer(input); + let value = U256::zero(); + let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + + run_call( + interpreter, + to, + interpreter.memory.slice(input).to_vec(), + scheme, + Weight::from_parts(gas_limit, u64::MAX), + value, + return_memory_range, + ) +} - let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), U256::ZERO) - else { - return; +fn run_call<'a, E: Ext>( + interpreter: &mut Interpreter<'a, E>, + callee: H160, + input: Vec, + scheme: CallScheme, + gas_limit: Weight, + value: U256, + return_memory_range: Range, +) -> ControlFlow { + let call_result = match scheme { + CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( + gas_limit, + U256::MAX, + &callee, + value, + input, + true, + scheme.is_static_call(), + ), + CallScheme::DelegateCall => + interpreter.ext.delegate_call(gas_limit, U256::MAX, callee, input), + CallScheme::CallCode => { + unreachable!() + }, }; - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input, - gas_limit, - target_address: to, - caller: Default::default(), - bytecode_address: to, - value: CallValue::Transfer(U256::ZERO), - scheme, - is_static: true, - return_memory_offset, - })))); + match call_result { + Ok(()) => { + let mem_start = return_memory_range.start; + let mem_length = return_memory_range.len(); + let returned_len = interpreter.ext.last_frame_output().data.len(); + let target_len = min(mem_length, returned_len); + + // success or revert + interpreter + .ext + .gas_meter_mut() + .charge_or_halt(RuntimeCosts::CopyToContract(target_len as u32))?; + + let return_value = interpreter.ext.last_frame_output(); + let return_data = &return_value.data; + let did_revert = return_value.did_revert(); + + // Note: This can't panic because we resized memory with `get_memory_in_and_out_ranges` + interpreter.memory.set(mem_start, &return_data[..target_len]); + interpreter.stack.push(U256::from(!did_revert as u8)) + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Call failed: {err:?}"); + interpreter.stack.push(U256::zero())?; + ControlFlow::Continue(()) + }, + } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs index 3a3d7284d2786..9796006882846 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs @@ -17,102 +17,94 @@ use crate::{ precompiles::{All as AllPrecompiles, Precompiles}, - vm::{evm::U256Converter, Ext}, - Pallet, RuntimeCosts, -}; -use core::ops::Range; -use revm::{ - interpreter::{ - interpreter_action::CallScheme, - interpreter_types::{MemoryTr, StackTr}, - Interpreter, + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt, Interpreter}, + Ext, }, - primitives::{Address, U256}, + Pallet, RuntimeCosts, }; -use sp_core::H160; +use core::ops::{ControlFlow, Range}; +use revm::interpreter::interpreter_action::CallScheme; +use sp_core::{H160, U256}; /// Gets memory input and output ranges for call instructions. -#[inline] -pub fn get_memory_input_and_out_ranges<'a, E: Ext>( - interpreter: &mut Interpreter>, -) -> Option<(Range, Range)> { - popn!([in_offset, in_len, out_offset, out_len], interpreter, None); - - let mut in_range = resize_memory(interpreter, in_offset, in_len)?; - - if !in_range.is_empty() { - let offset = <_ as MemoryTr>::local_memory_offset(&interpreter.memory); - in_range = in_range.start.saturating_add(offset)..in_range.end.saturating_add(offset); - } - +pub fn get_memory_in_and_out_ranges<'a, E: Ext>( + interpreter: &mut Interpreter<'a, E>, +) -> ControlFlow, Range)> { + let [in_offset, in_len, out_offset, out_len] = interpreter.stack.popn()?; + let in_range = resize_memory(interpreter, in_offset, in_len)?; let ret_range = resize_memory(interpreter, out_offset, out_len)?; - Some((in_range, ret_range)) + ControlFlow::Continue((in_range, ret_range)) } /// Resize memory and return range of memory. /// If `len` is 0 dont touch memory and return `usize::MAX` as offset and 0 as length. -#[inline] pub fn resize_memory<'a, E: Ext>( - interpreter: &mut Interpreter>, + interpreter: &mut Interpreter<'a, E>, offset: U256, len: U256, -) -> Option> { - let len = as_usize_or_fail_ret!(interpreter, len, None); - let offset = if len != 0 { - let offset = as_usize_or_fail_ret!(interpreter, offset, None); - resize_memory!(interpreter, offset, len, None); - offset +) -> ControlFlow> { + let len = as_usize_or_halt::(len)?; + if len != 0 { + let offset = as_usize_or_halt::(offset)?; + interpreter.memory.resize(offset, len)?; + ControlFlow::Continue(offset..offset + len) } else { - usize::MAX //unrealistic value so we are sure it is not used - }; - Some(offset..offset + len) + //unrealistic value so we are sure it is not used + ControlFlow::Continue(usize::MAX..usize::MAX) + } } /// Calculates gas cost and limit for call instructions. -#[inline] pub fn calc_call_gas<'a, E: Ext>( - interpreter: &mut Interpreter>, - callee: Address, + interpreter: &mut Interpreter<'a, E>, + callee: H160, scheme: CallScheme, input_len: usize, value: U256, -) -> Option { - let callee: H160 = callee.0 .0.into(); +) -> ControlFlow { let precompile = >::get::(&callee.as_fixed_bytes()); match precompile { Some(precompile) => { // Base cost depending on contract info - let base_cost = if precompile.has_contract_info() { - RuntimeCosts::PrecompileWithInfoBase - } else { - RuntimeCosts::PrecompileBase - }; - gas!(interpreter, base_cost, None); + interpreter + .ext + .gas_meter_mut() + .charge_or_halt(if precompile.has_contract_info() { + RuntimeCosts::PrecompileWithInfoBase + } else { + RuntimeCosts::PrecompileBase + })?; // Cost for decoding input - gas!(interpreter, RuntimeCosts::PrecompileDecode(input_len as u32), None); + interpreter + .ext + .gas_meter_mut() + .charge_or_halt(RuntimeCosts::PrecompileDecode(input_len as u32))?; }, None => { // Regular CALL / DELEGATECALL base cost / CALLCODE not supported - let base_cost = if scheme.is_delegate_call() { + interpreter.ext.charge_or_halt(if scheme.is_delegate_call() { RuntimeCosts::DelegateCallBase } else { RuntimeCosts::CallBase - }; - gas!(interpreter, base_cost, None); + })?; - gas!(interpreter, RuntimeCosts::CopyFromContract(input_len as u32), None); + interpreter + .ext + .gas_meter_mut() + .charge_or_halt(RuntimeCosts::CopyFromContract(input_len as u32))?; }, }; if !value.is_zero() { - gas!( - interpreter, - RuntimeCosts::CallTransferSurcharge { - dust_transfer: Pallet::::has_dust(crate::U256::from_revm_u256(&value)), - }, - None - ); + interpreter + .ext + .gas_meter_mut() + .charge_or_halt(RuntimeCosts::CallTransferSurcharge { + dust_transfer: Pallet::::has_dust(value), + })?; } - Some(u64::MAX) // TODO: Set the right gas limit + + ControlFlow::Continue(u64::MAX) // TODO: Set the right gas limit } diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 11298ea1f35f2..3dab142b57ffa 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -15,132 +15,118 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; -use crate::vm::Ext; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_action::InterpreterAction, - interpreter_types::{Jumps, LoopControl, StackTr}, - InstructionResult, Interpreter, +use crate::{ + vm::{ + evm::{ + interpreter::Halt, + util::{as_usize_or_halt, as_usize_or_halt_with}, + EVMGas, Interpreter, + }, + Ext, }, - primitives::{Bytes, U256}, + Error, U256, }; +use alloc::vec::Vec; +use core::ops::ControlFlow; +use revm::interpreter::gas::{BASE, HIGH, JUMPDEST, MID}; /// Implements the JUMP instruction. /// /// Unconditional jump to a valid destination. -pub fn jump<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::MID); - let Some([target]) = <_ as StackTr>::popn(&mut context.interpreter.stack) else { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; - }; - jump_inner(context.interpreter, target); +pub fn jump(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(MID))?; + let [target] = interpreter.stack.popn()?; + jump_inner(interpreter, target)?; + ControlFlow::Continue(()) } /// Implements the JUMPI instruction. /// /// Conditional jump to a valid destination if condition is true. -pub fn jumpi<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::HIGH); - let Some([target, cond]) = <_ as StackTr>::popn(&mut context.interpreter.stack) else { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; - }; +pub fn jumpi(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(HIGH))?; + let [target, cond] = interpreter.stack.popn()?; if !cond.is_zero() { - jump_inner(context.interpreter, target); + jump_inner(interpreter, target)?; } + ControlFlow::Continue(()) } #[inline(always)] /// Internal helper function for jump operations. /// /// Validates jump target and performs the actual jump. -fn jump_inner( - interpreter: &mut Interpreter, - target: U256, -) { - let target = as_usize_or_fail!(interpreter, target, InstructionResult::InvalidJump); +fn jump_inner(interpreter: &mut Interpreter, target: U256) -> ControlFlow { + let target = as_usize_or_halt_with(target, || Error::::InvalidJump.into())?; + if !interpreter.bytecode.is_valid_legacy_jump(target) { - interpreter.halt(InstructionResult::InvalidJump); - return; + return ControlFlow::Break(Error::::InvalidJump.into()); } // SAFETY: `is_valid_jump` ensures that `dest` is in bounds. interpreter.bytecode.absolute_jump(target); + ControlFlow::Continue(()) } /// Implements the JUMPDEST instruction. /// /// Marks a valid destination for jump operations. -pub fn jumpdest<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::JUMPDEST); +pub fn jumpdest(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(JUMPDEST))?; + ControlFlow::Continue(()) } /// Implements the PC instruction. /// /// Pushes the current program counter onto the stack. -pub fn pc<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); +pub fn pc(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; // - 1 because we have already advanced the instruction pointer in `Interpreter::step` - push!(context.interpreter, U256::from(context.interpreter.bytecode.pc() - 1)); + interpreter.stack.push(U256::from(interpreter.bytecode.pc() - 1))?; + ControlFlow::Continue(()) } #[inline] /// Internal helper function for return operations. /// /// Handles memory data retrieval and sets the return action. -fn return_inner<'a, E: Ext>( - interpreter: &mut Interpreter>, - instruction_result: InstructionResult, -) { - // Zero gas cost - // gas_legacy!(interpreter, revm_gas::ZERO) - let Some([offset, len]) = <_ as StackTr>::popn(&mut interpreter.stack) else { - interpreter.halt(InstructionResult::StackUnderflow); - return; - }; - let len = as_usize_or_fail!(interpreter, len); +fn return_inner( + interpreter: &mut Interpreter, + halt: impl Fn(Vec) -> Halt, +) -> ControlFlow { + let [offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt::(len)?; + // Important: Offset must be ignored if len is zeros - let mut output = Bytes::default(); + let mut output = Default::default(); if len != 0 { - let offset = as_usize_or_fail!(interpreter, offset); - resize_memory!(interpreter, offset, len); - output = interpreter.memory.slice_len(offset, len).to_vec().into() + let offset = as_usize_or_halt::(offset)?; + interpreter.memory.resize(offset, len)?; + output = interpreter.memory.slice_len(offset, len).to_vec() } - interpreter.bytecode.set_action(InterpreterAction::new_return( - instruction_result, - output, - interpreter.gas, - )); + ControlFlow::Break(halt(output)) } /// Implements the RETURN instruction. /// /// Halts execution and returns data from memory. -pub fn ret<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - return_inner(context.interpreter, InstructionResult::Return); +pub fn ret(interpreter: &mut Interpreter) -> ControlFlow { + return_inner(interpreter, Halt::Return) } /// EIP-140: REVERT instruction -pub fn revert<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - return_inner(context.interpreter, InstructionResult::Revert); +pub fn revert(interpreter: &mut Interpreter) -> ControlFlow { + return_inner(interpreter, Halt::Revert) } /// Stop opcode. This opcode halts the execution. -pub fn stop<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(InstructionResult::Stop); +pub fn stop(_interpreter: &mut Interpreter) -> ControlFlow { + ControlFlow::Break(Halt::Stop) } /// Invalid opcode. This opcode halts the execution. -pub fn invalid<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.extend.gas_meter_mut().consume_all(); - context.interpreter.halt(InstructionResult::InvalidFEOpcode); -} - -/// Unknown opcode. This opcode halts the execution. -pub fn unknown<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(InstructionResult::OpcodeNotFound); +pub fn invalid(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.gas_meter_mut().consume_all(); + ControlFlow::Break(Error::::InvalidInstruction.into()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index 6e2a3abb8bd16..b14dc9ff8f4c0 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -14,251 +14,242 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -use super::Context; - use crate::{ storage::WriteOutcome, vec::Vec, - vm::{evm::U256Converter, Ext}, - DispatchError, Key, RuntimeCosts, -}; -use revm::{ - interpreter::{interpreter_types::StackTr, InstructionResult}, - primitives::{Bytes, U256}, + vm::{ + evm::{ + instructions::utility::IntoAddress, interpreter::Halt, util::as_usize_or_halt, + Interpreter, + }, + Ext, + }, + DispatchError, Error, Key, RuntimeCosts, U256, }; +use core::ops::ControlFlow; /// Implements the BALANCE instruction. /// /// Gets the balance of the given account. -pub fn balance<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BalanceOf); - popn_top!([], top, context.interpreter); - let h160 = sp_core::H160::from_slice(&top.to_be_bytes::<32>()[12..]); - *top = context.interpreter.extend.balance_of(&h160).into_revm_u256(); +pub fn balance(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::BalanceOf)?; + let ([], top) = interpreter.stack.popn_top()?; + *top = interpreter.ext.balance_of(&top.into_address()); + ControlFlow::Continue(()) } /// EIP-1884: Repricing for trie-size-dependent opcodes -pub fn selfbalance<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Balance); - let balance = context.interpreter.extend.balance(); - push!(context.interpreter, balance.into_revm_u256()); +pub fn selfbalance(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::Balance)?; + let balance = interpreter.ext.balance(); + interpreter.stack.push(balance) } /// Implements the EXTCODESIZE instruction. /// /// Gets the size of an account's code. -pub fn extcodesize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], top, context.interpreter); - gas!(context.interpreter, RuntimeCosts::CodeSize); - let h160 = sp_core::H160::from_slice(&top.to_be_bytes::<32>()[12..]); - let code_size = context.interpreter.extend.code_size(&h160); +pub fn extcodesize(interpreter: &mut Interpreter) -> ControlFlow { + let ([], top) = interpreter.stack.popn_top()?; + interpreter.ext.charge_or_halt(RuntimeCosts::CodeSize)?; + let code_size = interpreter.ext.code_size(&top.into_address()); *top = U256::from(code_size); + ControlFlow::Continue(()) } /// EIP-1052: EXTCODEHASH opcode -pub fn extcodehash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], top, context.interpreter); - gas!(context.interpreter, RuntimeCosts::CodeHash); - let h160 = sp_core::H160::from_slice(&top.to_be_bytes::<32>()[12..]); - let code_hash = context.interpreter.extend.code_hash(&h160); - *top = U256::from_be_bytes(code_hash.0); +pub fn extcodehash(interpreter: &mut Interpreter) -> ControlFlow { + let ([], top) = interpreter.stack.popn_top()?; + interpreter.ext.charge_or_halt(RuntimeCosts::CodeHash)?; + let code_hash = interpreter.ext.code_hash(&top.into_address()); + *top = U256::from_big_endian(&code_hash.0); + ControlFlow::Continue(()) } /// Implements the EXTCODECOPY instruction. /// /// Copies a portion of an account's code to memory. -pub fn extcodecopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([address, memory_offset, code_offset, len_u256], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len_u256); - - gas!(context.interpreter, RuntimeCosts::ExtCodeCopy(len as u32)); - let address = sp_core::H160::from_slice(&address.to_be_bytes::<32>()[12..]); - +pub fn extcodecopy(interpreter: &mut Interpreter) -> ControlFlow { + let [address, memory_offset, code_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt::(len)?; + interpreter.ext.charge_or_halt(RuntimeCosts::ExtCodeCopy(len as u32))?; if len == 0 { - return; + return ControlFlow::Continue(()); } - let memory_offset = as_usize_or_fail!(context.interpreter, memory_offset); - let code_offset = as_usize_saturated!(code_offset); - resize_memory!(context.interpreter, memory_offset, len); + let address = address.into_address(); + let memory_offset = as_usize_or_halt::(memory_offset)?; + let code_offset = as_usize_or_halt::(code_offset)?; + + interpreter.memory.resize(memory_offset, len)?; - let mut buf = context.interpreter.memory.slice_mut(memory_offset, len); + let mut buf = interpreter.memory.slice_mut(memory_offset, len); // Note: This can't panic because we resized memory to fit. - context.interpreter.extend.copy_code_slice(&mut buf, &address, code_offset); + interpreter.ext.copy_code_slice(&mut buf, &address, code_offset); + ControlFlow::Continue(()) } /// Implements the BLOCKHASH instruction. /// /// Gets the hash of one of the 256 most recent complete blocks. -pub fn blockhash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::BlockHash); - popn_top!([], number, context.interpreter); - let requested_number = ::from_revm_u256(&number); +pub fn blockhash(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::BlockHash)?; + let ([], number) = interpreter.stack.popn_top()?; // blockhash should push zero if number is not within valid range. - if let Some(hash) = context.interpreter.extend.block_hash(requested_number) { - *number = U256::from_be_bytes(hash.0) + if let Some(hash) = interpreter.ext.block_hash(*number) { + *number = U256::from_big_endian(&hash.0) } else { - *number = U256::ZERO + *number = U256::zero() }; + ControlFlow::Continue(()) } /// Implements the SLOAD instruction. /// /// Loads a word from storage. -pub fn sload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], index, context.interpreter); +pub fn sload(interpreter: &mut Interpreter) -> ControlFlow { + let ([], index) = interpreter.stack.popn_top()?; // NB: SLOAD loads 32 bytes from storage (i.e. U256). - gas!(context.interpreter, RuntimeCosts::GetStorage(32)); - let key = Key::Fix(index.to_be_bytes()); - let value = context.interpreter.extend.get_storage(&key); + interpreter.ext.charge_or_halt(RuntimeCosts::GetStorage(32))?; + let key = Key::Fix(index.to_big_endian()); + let value = interpreter.ext.get_storage(&key); *index = if let Some(storage_value) = value { // sload always reads a word let Ok::<[u8; 32], _>(bytes) = storage_value.try_into() else { log::debug!(target: crate::LOG_TARGET, "sload read invalid storage value length. Expected 32."); - context.interpreter.halt(InstructionResult::FatalExternalError); - return + return ControlFlow::Break(Error::::ContractTrapped.into()); }; - U256::from_be_bytes(bytes) + U256::from_big_endian(&bytes) } else { // the key was never written before - U256::ZERO + U256::zero() }; + ControlFlow::Continue(()) } fn store_helper<'ext, E: Ext>( - context: Context<'_, 'ext, E>, + interpreter: &mut Interpreter<'ext, E>, cost_before: RuntimeCosts, set_function: fn(&mut E, &Key, Option>, bool) -> Result, adjust_cost: fn(new_bytes: u32, old_bytes: u32) -> RuntimeCosts, -) { - if context.interpreter.extend.is_read_only() { - context.interpreter.halt(InstructionResult::Revert); - return; +) -> ControlFlow { + if interpreter.ext.is_read_only() { + return ControlFlow::Break(Error::::StateChangeDenied.into()); } - popn!([index, value], context.interpreter); + let [index, value] = interpreter.stack.popn()?; // Charge gas before set_storage and later adjust it down to the true gas cost - let Ok(charged_amount) = context.interpreter.extend.gas_meter_mut().charge(cost_before) else { - context.interpreter.halt(InstructionResult::OutOfGas); - return; - }; - - let key = Key::Fix(index.to_be_bytes()); + let charged_amount = interpreter.ext.charge_or_halt(cost_before)?; + let key = Key::Fix(index.to_big_endian()); let take_old = false; - let Ok(write_outcome) = set_function( - context.interpreter.extend, - &key, - Some(value.to_be_bytes::<32>().to_vec()), - take_old, - ) else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return; + let Ok(write_outcome) = + set_function(interpreter.ext, &key, Some(value.to_big_endian().to_vec()), take_old) + else { + return ControlFlow::Break(Error::::ContractTrapped.into()); }; - context - .interpreter - .extend + interpreter + .ext .gas_meter_mut() .adjust_gas(charged_amount, adjust_cost(32, write_outcome.old_len())); + + ControlFlow::Continue(()) } /// Implements the SSTORE instruction. /// /// Stores a word to storage. -pub fn sstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - let old_bytes = context.interpreter.extend.max_value_size(); +pub fn sstore(interpreter: &mut Interpreter) -> ControlFlow { + let old_bytes = interpreter.ext.max_value_size(); store_helper( - context, + interpreter, RuntimeCosts::SetStorage { new_bytes: 32, old_bytes }, |ext, key, value, take_old| ext.set_storage(key, value, take_old), |new_bytes, old_bytes| RuntimeCosts::SetStorage { new_bytes, old_bytes }, - ); + ) } /// EIP-1153: Transient storage opcodes /// Store value to transient storage -pub fn tstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - let old_bytes = context.interpreter.extend.max_value_size(); +pub fn tstore(interpreter: &mut Interpreter) -> ControlFlow { + let old_bytes = interpreter.ext.max_value_size(); store_helper( - context, + interpreter, RuntimeCosts::SetTransientStorage { new_bytes: 32, old_bytes }, |ext, key, value, take_old| ext.set_transient_storage(key, value, take_old), |new_bytes, old_bytes| RuntimeCosts::SetTransientStorage { new_bytes, old_bytes }, - ); + ) } /// EIP-1153: Transient storage opcodes /// Load value from transient storage -pub fn tload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([], index, context.interpreter); - gas!(context.interpreter, RuntimeCosts::GetTransientStorage(32)); +pub fn tload(interpreter: &mut Interpreter) -> ControlFlow { + let ([], index) = interpreter.stack.popn_top()?; + interpreter.ext.charge_or_halt(RuntimeCosts::GetTransientStorage(32))?; + + let key = Key::Fix(index.to_big_endian()); + let bytes = interpreter.ext.get_transient_storage(&key); - let key = Key::Fix(index.to_be_bytes()); - let bytes = context.interpreter.extend.get_transient_storage(&key); *index = if let Some(storage_value) = bytes { if storage_value.len() != 32 { // tload always reads a word - log::error!(target: crate::LOG_TARGET, "tload read invalid storage value length. Expected 32."); - context.interpreter.halt(InstructionResult::FatalExternalError); - return; + log::debug!(target: crate::LOG_TARGET, "tload read invalid storage value length. Expected 32."); + return ControlFlow::Break(Error::::ContractTrapped.into()); } - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(&storage_value); - U256::from_be_bytes(bytes) + + let Ok::<[u8; 32], _>(bytes) = storage_value.try_into() else { + return ControlFlow::Break(Error::::ContractTrapped.into()); + }; + U256::from_big_endian(&bytes) } else { // the key was never written before - U256::ZERO + U256::zero() }; + ControlFlow::Continue(()) } /// Implements the LOG0-LOG4 instructions. /// /// Appends log record with N topics. -pub fn log<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - if context.interpreter.extend.is_read_only() { - context.interpreter.halt(InstructionResult::Revert); - return; +pub fn log<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + if interpreter.ext.is_read_only() { + return ControlFlow::Break(Error::::StateChangeDenied.into()); } - popn!([offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); - if len as u32 > context.interpreter.extend.max_value_size() { - context - .interpreter - .halt(revm::interpreter::InstructionResult::InvalidOperandOOG); - return; + let [offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt::(len)?; + if len as u32 > interpreter.ext.max_value_size() { + return ControlFlow::Break(Error::::OutOfGas.into()); } - gas!(context.interpreter, RuntimeCosts::DepositEvent { num_topic: N as u32, len: len as u32 }); + let cost = RuntimeCosts::DepositEvent { num_topic: N as u32, len: len as u32 }; + interpreter.ext.charge_or_halt(cost)?; + let data = if len == 0 { - Bytes::new() + Vec::new() } else { - let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, len); - Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref()) + let offset = as_usize_or_halt::(offset)?; + interpreter.memory.resize(offset, len)?; + interpreter.memory.slice(offset..offset + len).to_vec() }; - if context.interpreter.stack.len() < N { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; + if interpreter.stack.len() < N { + return ControlFlow::Break(Error::::StackUnderflow.into()); } - let Some(topics) = <_ as StackTr>::popn::(&mut context.interpreter.stack) else { - context.interpreter.halt(InstructionResult::StackUnderflow); - return; - }; - - let topics = topics.into_iter().map(|v| sp_core::H256::from(v.to_be_bytes())).collect(); + let topics = interpreter.stack.popn::()?; + let topics = topics.into_iter().map(|v| sp_core::H256::from(v.to_big_endian())).collect(); - context.interpreter.extend.deposit_event(topics, data.to_vec()); + interpreter.ext.deposit_event(topics, data.to_vec()); + ControlFlow::Continue(()) } /// Implements the SELFDESTRUCT instruction. /// /// Halt execution and register account for later deletion. -pub fn selfdestruct<'ext, E: Ext>(context: Context<'_, 'ext, E>) { +pub fn selfdestruct<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { // TODO: for now this instruction is not supported - context.interpreter.halt(InstructionResult::NotActivated); + ControlFlow::Break(Error::::InvalidInstruction.into()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/macros.rs b/substrate/frame/revive/src/vm/evm/instructions/macros.rs deleted file mode 100644 index e686e3f4be2ce..0000000000000 --- a/substrate/frame/revive/src/vm/evm/instructions/macros.rs +++ /dev/null @@ -1,238 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Utility macros to help implementing opcode instruction functions. - -/// `const` Option `?`. -#[macro_export] -macro_rules! tri { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - -/// Macro for optional try - returns early if the expression evaluates to None. -/// Similar to the `?` operator but for use in instruction implementations. -#[macro_export] -macro_rules! otry { - ($expression: expr) => {{ - let Some(value) = $expression else { - return; - }; - value - }}; -} - -/// Error if the current call is executing EOF. -#[macro_export] -macro_rules! require_eof { - ($interpreter:expr) => { - if !$interpreter.runtime_flag.is_eof() { - $interpreter.halt(revm::interpreter::InstructionResult::EOFOpcodeDisabledInLegacy); - return; - } - }; -} - -/// Check if the `SPEC` is enabled, and fail the instruction if it is not. -#[macro_export] -macro_rules! check { - ($interpreter:expr, $min:ident) => { - if !$interpreter - .runtime_flag - .spec_id() - .is_enabled_in(revm::primitives::hardfork::SpecId::$min) - { - $interpreter.halt(revm::interpreter::InstructionResult::NotActivated); - return; - } - }; -} - -/// Records a `gas` cost and fails the instruction if it would exceed the available gas. -#[macro_export] -macro_rules! gas_legacy { - ($interpreter:expr, $gas:expr) => { - gas_legacy!($interpreter, $gas, ()) - }; - ($interpreter:expr, $gas:expr, $ret:expr) => { - if $interpreter.extend.gas_meter_mut().charge_evm_gas($gas).is_err() { - $interpreter.halt(revm::interpreter::InstructionResult::OutOfGas); - return $ret; - } - }; -} - -#[macro_export] -macro_rules! gas { - ($interpreter:expr, $gas:expr) => { - gas!($interpreter, $gas, ()) - }; - ($interpreter:expr, $gas:expr, $ret:expr) => { - let meter = $interpreter.extend.gas_meter_mut(); - if meter.charge_evm_gas(1).is_err() || meter.charge($gas).is_err() { - $interpreter.halt(revm::interpreter::InstructionResult::OutOfGas); - return $ret; - } - }; -} - -/// Same as [`gas_legacy!`], but with `gas` as an option. -#[macro_export] -macro_rules! gas_or_fail_legacy { - ($interpreter:expr, $gas:expr) => { - gas_or_fail_legacy!($interpreter, $gas, ()) - }; - ($interpreter:expr, $gas:expr, $ret:expr) => { - match $gas { - Some(gas_used) => gas_legacy!($interpreter, gas_used, $ret), - None => { - $interpreter.halt(revm::interpreter::InstructionResult::OutOfGas); - return $ret; - }, - } - }; -} - -/// Resizes the interpreterreter memory if necessary. Fails the instruction if the memory or gas -/// limit is exceeded. -#[macro_export] -macro_rules! resize_memory { - ($interpreter:expr, $offset:expr, $len:expr) => { - resize_memory!($interpreter, $offset, $len, ()) - }; - ($interpreter:expr, $offset:expr, $len:expr, $ret:expr) => { - let current_len = $interpreter.memory.len(); - let target_len = revm::interpreter::num_words($offset.saturating_add($len)) * 32; - if target_len as u32 > $crate::limits::code::BASELINE_MEMORY_LIMIT { - log::debug!(target: $crate::LOG_TARGET, "check memory bounds failed: offset={} target_len={target_len} current_len={current_len}", $offset); - $interpreter.halt(revm::interpreter::InstructionResult::MemoryOOG); - return $ret; - } - - if target_len > current_len { - $interpreter.memory.resize(target_len); - }; - }; -} - -/// Pops n values from the stack. Fails the instruction if n values can't be popped. -#[macro_export] -macro_rules! popn { - ([ $($x:ident),* ],$interpreterreter:expr $(,$ret:expr)? ) => { - let Some([$( $x ),*]) = <_ as StackTr>::popn(&mut $interpreterreter.stack) else { - $interpreterreter.halt(revm::interpreter::InstructionResult::StackUnderflow); - return $($ret)?; - }; - }; -} - -/// Pops n values from the stack and returns the top value. Fails the instruction if n values can't -/// be popped. -#[macro_export] -macro_rules! popn_top { - ([ $($x:ident),* ], $top:ident, $interpreter:expr $(,$ret:expr)? ) => { - let Some(([$($x),*], $top)) = <_ as StackTr>::popn_top(&mut $interpreter.stack) else { - $interpreter.halt(revm::interpreter::InstructionResult::StackUnderflow); - return $($ret)?; - }; - }; -} - -/// Pushes a `B256` value onto the stack. Fails the instruction if the stack is full. -#[macro_export] -macro_rules! push { - ($interpreter:expr, $x:expr $(,$ret:item)?) => ( - if !($interpreter.stack.push($x)) { - $interpreter.halt(revm::interpreter::InstructionResult::StackOverflow); - return $($ret)?; - } - ) -} - -/// Converts a `U256` value to a `u64`, saturating to `MAX` if the value is too large. -#[macro_export] -macro_rules! as_u64_saturated { - ($v:expr) => { - match $v.as_limbs() { - x => - if (x[1] == 0) & (x[2] == 0) & (x[3] == 0) { - x[0] - } else { - u64::MAX - }, - } - }; -} - -/// Converts a `U256` value to a `usize`, saturating to `MAX` if the value is too large. -#[macro_export] -macro_rules! as_usize_saturated { - ($v:expr) => { - usize::try_from(as_u64_saturated!($v)).unwrap_or(usize::MAX) - }; -} - -/// Converts a `U256` value to a `isize`, saturating to `isize::MAX` if the value is too large. -#[macro_export] -macro_rules! as_isize_saturated { - ($v:expr) => { - // `isize_try_from(u64::MAX)`` will fail and return isize::MAX - // This is expected behavior as we are saturating the value. - isize::try_from(as_u64_saturated!($v)).unwrap_or(isize::MAX) - }; -} - -/// Converts a `U256` value to a `usize`, failing the instruction if the value is too large. -#[macro_export] -macro_rules! as_usize_or_fail { - ($interpreter:expr, $v:expr) => { - as_usize_or_fail_ret!($interpreter, $v, ()) - }; - ($interpreter:expr, $v:expr, $reason:expr) => { - as_usize_or_fail_ret!($interpreter, $v, $reason, ()) - }; -} - -/// Converts a `U256` value to a `usize` and returns `ret`, -/// failing the instruction if the value is too large. -#[macro_export] -macro_rules! as_usize_or_fail_ret { - ($interpreter:expr, $v:expr, $ret:expr) => { - as_usize_or_fail_ret!( - $interpreter, - $v, - revm::interpreter::InstructionResult::InvalidOperandOOG, - $ret - ) - }; - - ($interpreter:expr, $v:expr, $reason:expr, $ret:expr) => { - match $v.as_limbs() { - x => { - if (x[0] > usize::MAX as u64) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) { - $interpreter.halt($reason); - return $ret; - } - x[0] as usize - }, - } - }; -} diff --git a/substrate/frame/revive/src/vm/evm/instructions/memory.rs b/substrate/frame/revive/src/vm/evm/instructions/memory.rs index 3feaf89be6181..150d547131e07 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/memory.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/memory.rs @@ -15,77 +15,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; -use crate::vm::Ext; -use core::cmp::max; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{MemoryTr, StackTr}, +use crate::{ + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt, EVMGas, Interpreter}, + Ext, }, - primitives::U256, + Error, U256, }; +use core::{cmp::max, ops::ControlFlow}; +use revm::interpreter::gas::{copy_cost_verylow, BASE, VERYLOW}; /// Implements the MLOAD instruction. /// /// Loads a 32-byte word from memory. -pub fn mload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], top, context.interpreter); - let offset: usize = as_usize_or_fail!(context.interpreter, top); - resize_memory!(context.interpreter, offset, 32); - *top = - U256::try_from_be_slice(context.interpreter.memory.slice_len(offset, 32).as_ref()).unwrap() +pub fn mload(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([], top) = interpreter.stack.popn_top()?; + let offset = as_usize_or_halt::(*top)?; + interpreter.memory.resize(offset, 32)?; + *top = U256::from_big_endian(interpreter.memory.slice_len(offset, 32)); + ControlFlow::Continue(()) } /// Implements the MSTORE instruction. /// /// Stores a 32-byte word to memory. -pub fn mstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn!([offset, value], context.interpreter); - let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, 32); - context.interpreter.memory.set(offset, &value.to_be_bytes::<32>()); +pub fn mstore(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let [offset, value] = interpreter.stack.popn()?; + let offset = as_usize_or_halt::(offset)?; + interpreter.memory.resize(offset, 32)?; + interpreter.memory.set(offset, &value.to_big_endian()); + ControlFlow::Continue(()) } /// Implements the MSTORE8 instruction. /// /// Stores a single byte to memory. -pub fn mstore8<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn!([offset, value], context.interpreter); - let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, 1); - context.interpreter.memory.set(offset, &[value.byte(0)]); +pub fn mstore8(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let [offset, value] = interpreter.stack.popn()?; + let offset = as_usize_or_halt::(offset)?; + interpreter.memory.resize(offset, 1)?; + interpreter.memory.set(offset, &[value.byte(0)]); + ControlFlow::Continue(()) } /// Implements the MSIZE instruction. /// /// Gets the size of active memory in bytes. -pub fn msize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.memory.size())); +pub fn msize(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + interpreter.stack.push(U256::from(interpreter.memory.size())) } /// Implements the MCOPY instruction. /// /// EIP-5656: Memory copying instruction that copies memory from one location to another. -pub fn mcopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([dst, src, len], context.interpreter); +pub fn mcopy(interpreter: &mut Interpreter) -> ControlFlow { + let [dst, src, len] = interpreter.stack.popn()?; // Into usize or fail - let len = as_usize_or_fail!(context.interpreter, len); + let len = as_usize_or_halt::(len)?; // Deduce gas - gas_or_fail_legacy!(context.interpreter, revm_gas::copy_cost_verylow(len)); + let Some(gas_cost) = copy_cost_verylow(len) else { + return ControlFlow::Break(Error::::OutOfGas.into()); + }; + interpreter.ext.charge_or_halt(EVMGas(gas_cost))?; if len == 0 { - return; + return ControlFlow::Continue(()); } - let dst = as_usize_or_fail!(context.interpreter, dst); - let src = as_usize_or_fail!(context.interpreter, src); + let dst = as_usize_or_halt::(dst)?; + let src = as_usize_or_halt::(src)?; // Resize memory - resize_memory!(context.interpreter, max(dst, src), len); + interpreter.memory.resize(max(dst, src), len)?; // Copy memory in place - context.interpreter.memory.copy(dst, src, len); + interpreter.memory.copy(dst, src, len); + ControlFlow::Continue(()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 826c1febd474d..531cb051d1a91 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -15,19 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! EVM opcode implementations. +/// EVM opcode implementations. +use super::interpreter::Interpreter; +use crate::vm::{evm::Halt, Ext}; +use revm::bytecode::opcode::*; -use crate::vm::{ - evm::{DummyHost, EVMInterpreter}, - Ext, -}; -use revm::interpreter::{Instruction, InstructionContext}; - -type Context<'ctx, 'ext, E> = - InstructionContext<'ctx, crate::vm::evm::DummyHost, crate::vm::evm::EVMInterpreter<'ext, E>>; - -#[macro_use] -mod macros; /// Arithmetic operations (ADD, SUB, MUL, DIV, etc.). mod arithmetic; /// Bitwise operations (AND, OR, XOR, NOT, etc.). @@ -39,9 +31,10 @@ mod contract; /// Control flow instructions (JUMP, JUMPI, REVERT, etc.). mod control; /// Host environment interactions (SLOAD, SSTORE, LOG, etc.). +#[cfg(feature = "runtime-benchmarks")] +pub mod host; +#[cfg(not(feature = "runtime-benchmarks"))] mod host; -/// Signed 256-bit integer operations. -mod i256; /// Memory operations (MLOAD, MSTORE, MSIZE, etc.). mod memory; /// Stack operations (PUSH, POP, DUP, SWAP, etc.). @@ -53,195 +46,172 @@ mod tx_info; /// Utility functions and helpers for instruction implementation. mod utility; -/// Returns the instruction table for the given spec. -pub const fn instruction_table<'a, E: Ext>() -> [Instruction, DummyHost>; 256] -{ - use revm::bytecode::opcode::*; - let mut table = [control::unknown as Instruction, DummyHost>; 256]; - - table[STOP as usize] = control::stop; - table[ADD as usize] = arithmetic::add; - table[MUL as usize] = arithmetic::mul; - table[SUB as usize] = arithmetic::sub; - table[DIV as usize] = arithmetic::div; - table[SDIV as usize] = arithmetic::sdiv; - table[MOD as usize] = arithmetic::rem; - table[SMOD as usize] = arithmetic::smod; - table[ADDMOD as usize] = arithmetic::addmod; - table[MULMOD as usize] = arithmetic::mulmod; - table[EXP as usize] = arithmetic::exp; - table[SIGNEXTEND as usize] = arithmetic::signextend; - - table[LT as usize] = bitwise::lt; - table[GT as usize] = bitwise::gt; - table[SLT as usize] = bitwise::slt; - table[SGT as usize] = bitwise::sgt; - table[EQ as usize] = bitwise::eq; - table[ISZERO as usize] = bitwise::iszero; - table[AND as usize] = bitwise::bitand; - table[OR as usize] = bitwise::bitor; - table[XOR as usize] = bitwise::bitxor; - table[NOT as usize] = bitwise::not; - table[BYTE as usize] = bitwise::byte; - table[SHL as usize] = bitwise::shl; - table[SHR as usize] = bitwise::shr; - table[SAR as usize] = bitwise::sar; - table[CLZ as usize] = bitwise::clz; - - table[KECCAK256 as usize] = system::keccak256; - - table[ADDRESS as usize] = system::address; - table[BALANCE as usize] = host::balance; - table[ORIGIN as usize] = tx_info::origin; - table[CALLER as usize] = system::caller; - table[CALLVALUE as usize] = system::callvalue; - table[CALLDATALOAD as usize] = system::calldataload; - table[CALLDATASIZE as usize] = system::calldatasize; - table[CALLDATACOPY as usize] = system::calldatacopy; - table[CODESIZE as usize] = system::codesize; - table[CODECOPY as usize] = system::codecopy; - - table[GASPRICE as usize] = tx_info::gasprice; - table[EXTCODESIZE as usize] = host::extcodesize; - table[EXTCODECOPY as usize] = host::extcodecopy; - table[RETURNDATASIZE as usize] = system::returndatasize; - table[RETURNDATACOPY as usize] = system::returndatacopy; - table[EXTCODEHASH as usize] = host::extcodehash; - table[BLOCKHASH as usize] = host::blockhash; - table[COINBASE as usize] = block_info::coinbase; - table[TIMESTAMP as usize] = block_info::timestamp; - table[NUMBER as usize] = block_info::block_number; - table[DIFFICULTY as usize] = block_info::difficulty; - table[GASLIMIT as usize] = block_info::gaslimit; - table[CHAINID as usize] = block_info::chainid; - table[SELFBALANCE as usize] = host::selfbalance; - table[BASEFEE as usize] = block_info::basefee; - table[BLOBHASH as usize] = tx_info::blob_hash; - table[BLOBBASEFEE as usize] = block_info::blob_basefee; - - table[POP as usize] = stack::pop; - table[MLOAD as usize] = memory::mload; - table[MSTORE as usize] = memory::mstore; - table[MSTORE8 as usize] = memory::mstore8; - table[SLOAD as usize] = host::sload; - table[SSTORE as usize] = host::sstore; - table[JUMP as usize] = control::jump; - table[JUMPI as usize] = control::jumpi; - table[PC as usize] = control::pc; - table[MSIZE as usize] = memory::msize; - table[GAS as usize] = system::gas; - table[JUMPDEST as usize] = control::jumpdest; - table[TLOAD as usize] = host::tload; - table[TSTORE as usize] = host::tstore; - table[MCOPY as usize] = memory::mcopy; - - table[PUSH0 as usize] = stack::push0; - table[PUSH1 as usize] = stack::push::<1, _>; - table[PUSH2 as usize] = stack::push::<2, _>; - table[PUSH3 as usize] = stack::push::<3, _>; - table[PUSH4 as usize] = stack::push::<4, _>; - table[PUSH5 as usize] = stack::push::<5, _>; - table[PUSH6 as usize] = stack::push::<6, _>; - table[PUSH7 as usize] = stack::push::<7, _>; - table[PUSH8 as usize] = stack::push::<8, _>; - table[PUSH9 as usize] = stack::push::<9, _>; - table[PUSH10 as usize] = stack::push::<10, _>; - table[PUSH11 as usize] = stack::push::<11, _>; - table[PUSH12 as usize] = stack::push::<12, _>; - table[PUSH13 as usize] = stack::push::<13, _>; - table[PUSH14 as usize] = stack::push::<14, _>; - table[PUSH15 as usize] = stack::push::<15, _>; - table[PUSH16 as usize] = stack::push::<16, _>; - table[PUSH17 as usize] = stack::push::<17, _>; - table[PUSH18 as usize] = stack::push::<18, _>; - table[PUSH19 as usize] = stack::push::<19, _>; - table[PUSH20 as usize] = stack::push::<20, _>; - table[PUSH21 as usize] = stack::push::<21, _>; - table[PUSH22 as usize] = stack::push::<22, _>; - table[PUSH23 as usize] = stack::push::<23, _>; - table[PUSH24 as usize] = stack::push::<24, _>; - table[PUSH25 as usize] = stack::push::<25, _>; - table[PUSH26 as usize] = stack::push::<26, _>; - table[PUSH27 as usize] = stack::push::<27, _>; - table[PUSH28 as usize] = stack::push::<28, _>; - table[PUSH29 as usize] = stack::push::<29, _>; - table[PUSH30 as usize] = stack::push::<30, _>; - table[PUSH31 as usize] = stack::push::<31, _>; - table[PUSH32 as usize] = stack::push::<32, _>; - - table[DUP1 as usize] = stack::dup::<1, _>; - table[DUP2 as usize] = stack::dup::<2, _>; - table[DUP3 as usize] = stack::dup::<3, _>; - table[DUP4 as usize] = stack::dup::<4, _>; - table[DUP5 as usize] = stack::dup::<5, _>; - table[DUP6 as usize] = stack::dup::<6, _>; - table[DUP7 as usize] = stack::dup::<7, _>; - table[DUP8 as usize] = stack::dup::<8, _>; - table[DUP9 as usize] = stack::dup::<9, _>; - table[DUP10 as usize] = stack::dup::<10, _>; - table[DUP11 as usize] = stack::dup::<11, _>; - table[DUP12 as usize] = stack::dup::<12, _>; - table[DUP13 as usize] = stack::dup::<13, _>; - table[DUP14 as usize] = stack::dup::<14, _>; - table[DUP15 as usize] = stack::dup::<15, _>; - table[DUP16 as usize] = stack::dup::<16, _>; - - table[SWAP1 as usize] = stack::swap::<1, _>; - table[SWAP2 as usize] = stack::swap::<2, _>; - table[SWAP3 as usize] = stack::swap::<3, _>; - table[SWAP4 as usize] = stack::swap::<4, _>; - table[SWAP5 as usize] = stack::swap::<5, _>; - table[SWAP6 as usize] = stack::swap::<6, _>; - table[SWAP7 as usize] = stack::swap::<7, _>; - table[SWAP8 as usize] = stack::swap::<8, _>; - table[SWAP9 as usize] = stack::swap::<9, _>; - table[SWAP10 as usize] = stack::swap::<10, _>; - table[SWAP11 as usize] = stack::swap::<11, _>; - table[SWAP12 as usize] = stack::swap::<12, _>; - table[SWAP13 as usize] = stack::swap::<13, _>; - table[SWAP14 as usize] = stack::swap::<14, _>; - table[SWAP15 as usize] = stack::swap::<15, _>; - table[SWAP16 as usize] = stack::swap::<16, _>; - - table[LOG0 as usize] = host::log::<0, _>; - table[LOG1 as usize] = host::log::<1, _>; - table[LOG2 as usize] = host::log::<2, _>; - table[LOG3 as usize] = host::log::<3, _>; - table[LOG4 as usize] = host::log::<4, _>; - - table[CREATE as usize] = contract::create::; - table[CALL as usize] = contract::call; - table[CALLCODE as usize] = contract::call_code; - table[RETURN as usize] = control::ret; - table[DELEGATECALL as usize] = contract::delegate_call; - table[CREATE2 as usize] = contract::create::; - - table[STATICCALL as usize] = contract::static_call; - table[REVERT as usize] = control::revert; - table[INVALID as usize] = control::invalid; - table[SELFDESTRUCT as usize] = host::selfdestruct; - table -} - -#[cfg(test)] -mod tests { - use super::instruction_table; - use revm::bytecode::opcode::*; - - #[test] - fn all_instructions_and_opcodes_used() { - // known unknown instruction we compare it with other instructions from table. - let unknown_instruction = 0x0C_usize; - - use crate::{exec::Stack, tests::Test, ContractBlob}; - let instr_table = instruction_table::<'static, Stack<'static, Test, ContractBlob>>(); - - let unknown_istr = instr_table[unknown_instruction]; - for (i, instr) in instr_table.iter().enumerate() { - let is_opcode_unknown = OpCode::new(i as u8).is_none(); - // - let is_instr_unknown = std::ptr::fn_addr_eq(*instr, unknown_istr); - assert_eq!(is_instr_unknown, is_opcode_unknown, "Opcode 0x{i:X?} is not handled",); - } +pub fn exec_instruction( + interpreter: &mut Interpreter, + opcode: u8, +) -> core::ops::ControlFlow { + match opcode { + STOP => control::stop(interpreter), + ADD => arithmetic::add(interpreter), + MUL => arithmetic::mul(interpreter), + SUB => arithmetic::sub(interpreter), + DIV => arithmetic::div(interpreter), + SDIV => arithmetic::sdiv(interpreter), + MOD => arithmetic::rem(interpreter), + SMOD => arithmetic::smod(interpreter), + ADDMOD => arithmetic::addmod(interpreter), + MULMOD => arithmetic::mulmod(interpreter), + EXP => arithmetic::exp(interpreter), + SIGNEXTEND => arithmetic::signextend(interpreter), + + LT => bitwise::lt(interpreter), + GT => bitwise::gt(interpreter), + SLT => bitwise::slt(interpreter), + SGT => bitwise::sgt(interpreter), + EQ => bitwise::eq(interpreter), + ISZERO => bitwise::iszero(interpreter), + AND => bitwise::bitand(interpreter), + OR => bitwise::bitor(interpreter), + XOR => bitwise::bitxor(interpreter), + NOT => bitwise::not(interpreter), + BYTE => bitwise::byte(interpreter), + SHL => bitwise::shl(interpreter), + SHR => bitwise::shr(interpreter), + SAR => bitwise::sar(interpreter), + CLZ => bitwise::clz(interpreter), + + KECCAK256 => system::keccak256(interpreter), + + ADDRESS => system::address(interpreter), + BALANCE => host::balance(interpreter), + ORIGIN => tx_info::origin(interpreter), + CALLER => system::caller(interpreter), + CALLVALUE => system::callvalue(interpreter), + CALLDATALOAD => system::calldataload(interpreter), + CALLDATASIZE => system::calldatasize(interpreter), + CALLDATACOPY => system::calldatacopy(interpreter), + CODESIZE => system::codesize(interpreter), + CODECOPY => system::codecopy(interpreter), + + GASPRICE => tx_info::gasprice(interpreter), + EXTCODESIZE => host::extcodesize(interpreter), + EXTCODECOPY => host::extcodecopy(interpreter), + RETURNDATASIZE => system::returndatasize(interpreter), + RETURNDATACOPY => system::returndatacopy(interpreter), + EXTCODEHASH => host::extcodehash(interpreter), + BLOCKHASH => host::blockhash(interpreter), + COINBASE => block_info::coinbase(interpreter), + TIMESTAMP => block_info::timestamp(interpreter), + NUMBER => block_info::block_number(interpreter), + DIFFICULTY => block_info::difficulty(interpreter), + GASLIMIT => block_info::gaslimit(interpreter), + CHAINID => block_info::chainid(interpreter), + SELFBALANCE => host::selfbalance(interpreter), + BASEFEE => block_info::basefee(interpreter), + BLOBHASH => tx_info::blob_hash(interpreter), + BLOBBASEFEE => block_info::blob_basefee(interpreter), + + POP => stack::pop(interpreter), + MLOAD => memory::mload(interpreter), + MSTORE => memory::mstore(interpreter), + MSTORE8 => memory::mstore8(interpreter), + SLOAD => host::sload(interpreter), + SSTORE => host::sstore(interpreter), + JUMP => control::jump(interpreter), + JUMPI => control::jumpi(interpreter), + PC => control::pc(interpreter), + MSIZE => memory::msize(interpreter), + GAS => system::gas(interpreter), + JUMPDEST => control::jumpdest(interpreter), + TLOAD => host::tload(interpreter), + TSTORE => host::tstore(interpreter), + MCOPY => memory::mcopy(interpreter), + + PUSH0 => stack::push0(interpreter), + PUSH1 => stack::push::<1, _>(interpreter), + PUSH2 => stack::push::<2, _>(interpreter), + PUSH3 => stack::push::<3, _>(interpreter), + PUSH4 => stack::push::<4, _>(interpreter), + PUSH5 => stack::push::<5, _>(interpreter), + PUSH6 => stack::push::<6, _>(interpreter), + PUSH7 => stack::push::<7, _>(interpreter), + PUSH8 => stack::push::<8, _>(interpreter), + PUSH9 => stack::push::<9, _>(interpreter), + PUSH10 => stack::push::<10, _>(interpreter), + PUSH11 => stack::push::<11, _>(interpreter), + PUSH12 => stack::push::<12, _>(interpreter), + PUSH13 => stack::push::<13, _>(interpreter), + PUSH14 => stack::push::<14, _>(interpreter), + PUSH15 => stack::push::<15, _>(interpreter), + PUSH16 => stack::push::<16, _>(interpreter), + PUSH17 => stack::push::<17, _>(interpreter), + PUSH18 => stack::push::<18, _>(interpreter), + PUSH19 => stack::push::<19, _>(interpreter), + PUSH20 => stack::push::<20, _>(interpreter), + PUSH21 => stack::push::<21, _>(interpreter), + PUSH22 => stack::push::<22, _>(interpreter), + PUSH23 => stack::push::<23, _>(interpreter), + PUSH24 => stack::push::<24, _>(interpreter), + PUSH25 => stack::push::<25, _>(interpreter), + PUSH26 => stack::push::<26, _>(interpreter), + PUSH27 => stack::push::<27, _>(interpreter), + PUSH28 => stack::push::<28, _>(interpreter), + PUSH29 => stack::push::<29, _>(interpreter), + PUSH30 => stack::push::<30, _>(interpreter), + PUSH31 => stack::push::<31, _>(interpreter), + PUSH32 => stack::push::<32, _>(interpreter), + + DUP1 => stack::dup::<1, _>(interpreter), + DUP2 => stack::dup::<2, _>(interpreter), + DUP3 => stack::dup::<3, _>(interpreter), + DUP4 => stack::dup::<4, _>(interpreter), + DUP5 => stack::dup::<5, _>(interpreter), + DUP6 => stack::dup::<6, _>(interpreter), + DUP7 => stack::dup::<7, _>(interpreter), + DUP8 => stack::dup::<8, _>(interpreter), + DUP9 => stack::dup::<9, _>(interpreter), + DUP10 => stack::dup::<10, _>(interpreter), + DUP11 => stack::dup::<11, _>(interpreter), + DUP12 => stack::dup::<12, _>(interpreter), + DUP13 => stack::dup::<13, _>(interpreter), + DUP14 => stack::dup::<14, _>(interpreter), + DUP15 => stack::dup::<15, _>(interpreter), + DUP16 => stack::dup::<16, _>(interpreter), + + SWAP1 => stack::swap::<1, _>(interpreter), + SWAP2 => stack::swap::<2, _>(interpreter), + SWAP3 => stack::swap::<3, _>(interpreter), + SWAP4 => stack::swap::<4, _>(interpreter), + SWAP5 => stack::swap::<5, _>(interpreter), + SWAP6 => stack::swap::<6, _>(interpreter), + SWAP7 => stack::swap::<7, _>(interpreter), + SWAP8 => stack::swap::<8, _>(interpreter), + SWAP9 => stack::swap::<9, _>(interpreter), + SWAP10 => stack::swap::<10, _>(interpreter), + SWAP11 => stack::swap::<11, _>(interpreter), + SWAP12 => stack::swap::<12, _>(interpreter), + SWAP13 => stack::swap::<13, _>(interpreter), + SWAP14 => stack::swap::<14, _>(interpreter), + SWAP15 => stack::swap::<15, _>(interpreter), + SWAP16 => stack::swap::<16, _>(interpreter), + + LOG0 => host::log::<0, _>(interpreter), + LOG1 => host::log::<1, _>(interpreter), + LOG2 => host::log::<2, _>(interpreter), + LOG3 => host::log::<3, _>(interpreter), + LOG4 => host::log::<4, _>(interpreter), + + CREATE => contract::create::(interpreter), + CREATE2 => contract::create::(interpreter), + CALL => contract::call(interpreter), + STATICCALL => contract::static_call(interpreter), + DELEGATECALL => contract::delegate_call(interpreter), + CALLCODE => contract::call_code(interpreter), + + RETURN => control::ret(interpreter), + REVERT => control::revert(interpreter), + SELFDESTRUCT => host::selfdestruct(interpreter), + + _ => control::invalid(interpreter), } } diff --git a/substrate/frame/revive/src/vm/evm/instructions/stack.rs b/substrate/frame/revive/src/vm/evm/instructions/stack.rs index 055c1401686c9..c29a220602b8b 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/stack.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/stack.rs @@ -15,66 +15,66 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{utility::cast_slice_to_u256, Context}; -use crate::vm::Ext; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{Immediates, Jumps, StackTr}, - InstructionResult, +use crate::{ + vm::{ + evm::{interpreter::Halt, EVMGas, Interpreter}, + Ext, }, - primitives::U256, + U256, }; +use core::ops::ControlFlow; +use revm::interpreter::gas::{BASE, VERYLOW}; /// Implements the POP instruction. /// /// Removes the top item from the stack. -pub fn pop<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - // Can ignore return. as relative N jump is safe operation. - popn!([_i], context.interpreter); +pub fn pop(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + let [_] = interpreter.stack.popn()?; + ControlFlow::Continue(()) } /// EIP-3855: PUSH0 instruction /// /// Introduce a new instruction which pushes the constant value 0 onto the stack. -pub fn push0<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::ZERO); +pub fn push0(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + interpreter.stack.push(U256::zero()) } /// Implements the PUSH1-PUSH32 instructions. /// /// Pushes N bytes from bytecode onto the stack as a 32-byte value. -pub fn push<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - push!(context.interpreter, U256::ZERO); - popn_top!([], top, context.interpreter); +pub fn push<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; - let imm = context.interpreter.bytecode.read_slice(N); - cast_slice_to_u256(imm, top); + let slice = interpreter.bytecode.read_slice(N); + interpreter.stack.push_slice(slice)?; // Can ignore return. as relative N jump is safe operation - context.interpreter.bytecode.relative_jump(N as isize); + interpreter.bytecode.relative_jump(N as isize); + ControlFlow::Continue(()) } /// Implements the DUP1-DUP16 instructions. /// /// Duplicates the Nth stack item to the top of the stack. -pub fn dup<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - if !context.interpreter.stack.dup(N) { - context.interpreter.halt(InstructionResult::StackOverflow); - } +pub fn dup<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + interpreter.stack.dup(N) } /// Implements the SWAP1-SWAP16 instructions. /// /// Swaps the top stack item with the Nth stack item. -pub fn swap<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); +pub fn swap<'ext, const N: usize, E: Ext>( + interpreter: &mut Interpreter<'ext, E>, +) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; assert!(N != 0); - if !context.interpreter.stack.exchange(0, N) { - context.interpreter.halt(InstructionResult::StackOverflow); - } + interpreter.stack.exchange(0, N) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/system.rs b/substrate/frame/revive/src/vm/evm/instructions/system.rs index 72c31c7955237..7f445e6535595 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/system.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/system.rs @@ -15,240 +15,211 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::Context; +use super::utility::as_usize_saturated; use crate::{ address::AddressMapper, - vm::{evm::U256Converter, Ext, RuntimeCosts}, - Config, -}; -use core::ptr; -use revm::{ - interpreter::{ - gas as revm_gas, - interpreter_types::{InputsTr, LegacyBytecode, MemoryTr, ReturnData, StackTr}, - CallInput, InstructionResult, Interpreter, + vm::{ + evm::{interpreter::Halt, util::as_usize_or_halt, EVMGas, Interpreter}, + Ext, RuntimeCosts, }, - primitives::{Address, B256, KECCAK_EMPTY, U256}, + Config, Error, U256, }; +use core::ops::ControlFlow; +use revm::interpreter::gas::{BASE, VERYLOW}; +use sp_core::H256; use sp_io::hashing::keccak_256; - // TODO: Fix the gas handling for the memory operations +/// The Keccak-256 hash of the empty string `""`. +pub const KECCAK_EMPTY: [u8; 32] = + alloy_core::hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + /// Implements the KECCAK256 instruction. /// /// Computes Keccak-256 hash of memory data. -pub fn keccak256<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn_top!([offset], top, context.interpreter); - let len = as_usize_or_fail!(context.interpreter, top); - gas!(context.interpreter, RuntimeCosts::HashKeccak256(len as u32)); +pub fn keccak256(interpreter: &mut Interpreter) -> ControlFlow { + let ([offset], top) = interpreter.stack.popn_top()?; + let len = as_usize_or_halt::(*top)?; + interpreter + .ext + .gas_meter_mut() + .charge_or_halt(RuntimeCosts::HashKeccak256(len as u32))?; + let hash = if len == 0 { - KECCAK_EMPTY + H256::from(KECCAK_EMPTY) } else { - let from = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, from, len); - keccak_256(context.interpreter.memory.slice_len(from, len).as_ref()).into() + let from = as_usize_or_halt::(offset)?; + interpreter.memory.resize(from, len)?; + H256::from(keccak_256(interpreter.memory.slice_len(from, len))) }; - *top = hash.into(); + *top = U256::from_big_endian(hash.as_ref()); + ControlFlow::Continue(()) } /// Implements the ADDRESS instruction. /// /// Pushes the current contract's address onto the stack. -pub fn address<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Address); - let address: Address = context.interpreter.extend.address().0.into(); - push!(context.interpreter, address.into_word().into()); +pub fn address(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::Address)?; + let address = interpreter.ext.address(); + interpreter.stack.push(address) } /// Implements the CALLER instruction. /// /// Pushes the caller's address onto the stack. -pub fn caller<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Caller); - match context.interpreter.extend.caller().account_id() { +pub fn caller(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::Caller)?; + match interpreter.ext.caller().account_id() { Ok(account_id) => { - let address: Address = ::AddressMapper::to_address(account_id).0.into(); - push!(context.interpreter, address.into_word().into()); - }, - Err(_) => { - context - .interpreter - .halt(revm::interpreter::InstructionResult::FatalExternalError); + let address = ::AddressMapper::to_address(account_id); + interpreter.stack.push(address) }, + Err(_) => ControlFlow::Break(Error::::ContractTrapped.into()), } } /// Implements the CODESIZE instruction. /// /// Pushes the size of running contract's bytecode onto the stack. -pub fn codesize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.bytecode.bytecode_len())); +pub fn codesize(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + interpreter.stack.push(U256::from(interpreter.bytecode.len())) } /// Implements the CODECOPY instruction. /// /// Copies running contract's bytecode to memory. -pub fn codecopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([memory_offset, code_offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); - let Some(memory_offset) = memory_resize(context.interpreter, memory_offset, len) else { - return; +pub fn codecopy(interpreter: &mut Interpreter) -> ControlFlow { + let [memory_offset, code_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt::(len)?; + let Some(memory_offset) = memory_resize(interpreter, memory_offset, len)? else { + return ControlFlow::Continue(()) }; - let code_offset = as_usize_saturated!(code_offset); + let code_offset = as_usize_saturated(code_offset); - // Note: This can't panic because we resized memory to fit. - context.interpreter.memory.set_data( + // Note: This can't panic because we resized memory. + interpreter.memory.set_data( memory_offset, code_offset, len, - context.interpreter.bytecode.bytecode_slice(), + interpreter.bytecode.bytecode_slice(), ); + ControlFlow::Continue(()) } /// Implements the CALLDATALOAD instruction. /// /// Loads 32 bytes of input data from the specified offset. -pub fn calldataload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - //pop_top!(interpreter, offset_ptr); - popn_top!([], offset_ptr, context.interpreter); - let mut word = B256::ZERO; - let offset = as_usize_saturated!(offset_ptr); - let input = context.interpreter.input.input(); +pub fn calldataload(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(VERYLOW))?; + let ([], offset_ptr) = interpreter.stack.popn_top()?; + let mut word = [0u8; 32]; + let offset = as_usize_saturated(*offset_ptr); + let input = &interpreter.input; let input_len = input.len(); if offset < input_len { let count = 32.min(input_len - offset); - - // SAFETY: `count` is bounded by the calldata length. - // This is `word[..count].copy_from_slice(input[offset..offset + count])`, written using - // raw pointers as apparently the compiler cannot optimize the slice version, and using - // `get_unchecked` twice is uglier. - match context.interpreter.input.input() { - CallInput::Bytes(bytes) => { - unsafe { - ptr::copy_nonoverlapping(bytes.as_ptr().add(offset), word.as_mut_ptr(), count) - }; - }, - CallInput::SharedBuffer(range) => { - let input_slice = context.interpreter.memory.global_slice(range.clone()); - unsafe { - ptr::copy_nonoverlapping( - input_slice.as_ptr().add(offset), - word.as_mut_ptr(), - count, - ) - }; - }, - } + word[..count].copy_from_slice(&input[offset..offset + count]); } - *offset_ptr = word.into(); + *offset_ptr = U256::from_big_endian(&word); + ControlFlow::Continue(()) } /// Implements the CALLDATASIZE instruction. /// /// Pushes the size of input data onto the stack. -pub fn calldatasize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.input.input().len())); +pub fn calldatasize(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + interpreter.stack.push(U256::from(interpreter.input.len())) } /// Implements the CALLVALUE instruction. /// /// Pushes the value sent with the current call onto the stack. -pub fn callvalue<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::ValueTransferred); - let call_value = context.interpreter.extend.value_transferred(); - push!(context.interpreter, call_value.into_revm_u256()); +pub fn callvalue(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::ValueTransferred)?; + let value = interpreter.ext.value_transferred(); + interpreter.stack.push(value) } /// Implements the CALLDATACOPY instruction. /// /// Copies input data to memory. -pub fn calldatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([memory_offset, data_offset, len], context.interpreter); - let len = as_usize_or_fail!(context.interpreter, len); - let Some(memory_offset) = memory_resize(context.interpreter, memory_offset, len) else { - return; +pub fn calldatacopy(interpreter: &mut Interpreter) -> ControlFlow { + let [memory_offset, data_offset, len] = interpreter.stack.popn()?; + let len = as_usize_or_halt::(len)?; + + let Some(memory_offset) = memory_resize(interpreter, memory_offset, len)? else { + return ControlFlow::Continue(()); }; - let data_offset = as_usize_saturated!(data_offset); - match context.interpreter.input.input() { - CallInput::Bytes(bytes) => { - context - .interpreter - .memory - .set_data(memory_offset, data_offset, len, bytes.as_ref()); - }, - CallInput::SharedBuffer(range) => { - context.interpreter.memory.set_data_from_global( - memory_offset, - data_offset, - len, - range.clone(), - ); - }, - } + let data_offset = as_usize_saturated(data_offset); + + // Note: This can't panic because we resized memory. + interpreter.memory.set_data(memory_offset, data_offset, len, &interpreter.input); + ControlFlow::Continue(()) } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY -pub fn returndatasize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.return_data.buffer().len())); +pub fn returndatasize(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(EVMGas(BASE))?; + let return_data_len = interpreter.ext.last_frame_output().data.len(); + interpreter.stack.push(U256::from(return_data_len)) } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY -pub fn returndatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([memory_offset, offset, len], context.interpreter); +pub fn returndatacopy(interpreter: &mut Interpreter) -> ControlFlow { + let [memory_offset, offset, len] = interpreter.stack.popn()?; - let len = as_usize_or_fail!(context.interpreter, len); - let data_offset = as_usize_saturated!(offset); + let len = as_usize_or_halt::(len)?; + let data_offset = as_usize_saturated(offset); // Old legacy behavior is to panic if data_end is out of scope of return buffer. let data_end = data_offset.saturating_add(len); - if data_end > context.interpreter.return_data.buffer().len() { - context.interpreter.halt(InstructionResult::OutOfOffset); - return; + if data_end > interpreter.ext.last_frame_output().data.len() { + return ControlFlow::Break(Error::::OutOfBounds.into()); } - let Some(memory_offset) = memory_resize(context.interpreter, memory_offset, len) else { - return; + let Some(memory_offset) = memory_resize(interpreter, memory_offset, len)? else { + return ControlFlow::Continue(()) }; - // Note: This can't panic because we resized memory to fit. - context.interpreter.memory.set_data( + // Note: This can't panic because we resized memory. + interpreter.memory.set_data( memory_offset, data_offset, len, - context.interpreter.return_data.buffer(), + &interpreter.ext.last_frame_output().data, ); + ControlFlow::Continue(()) } /// Implements the GAS instruction. /// /// Pushes the amount of remaining gas onto the stack. -pub fn gas<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::RefTimeLeft); +pub fn gas(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::RefTimeLeft)?; // TODO: This accounts only for 'ref_time' now. It should be fixed to also account for other // costs. See #9577 for more context. - let gas = context.interpreter.extend.gas_meter().gas_left().ref_time(); - push!(context.interpreter, U256::from(gas)); + let gas = interpreter.ext.gas_meter().gas_left().ref_time(); + interpreter.stack.push(U256::from(gas)) } /// Common logic for copying data from a source buffer to the EVM's memory. /// /// Handles memory expansion and gas calculation for data copy operations. pub fn memory_resize<'a, E: Ext>( - interpreter: &mut Interpreter>, + interpreter: &mut Interpreter<'a, E>, memory_offset: U256, len: usize, -) -> Option { - gas!(interpreter, RuntimeCosts::CopyToContract(len as u32), None); +) -> ControlFlow> { if len == 0 { - return None; + return ControlFlow::Continue(None) } - let memory_offset = as_usize_or_fail_ret!(interpreter, memory_offset, None); - resize_memory!(interpreter, memory_offset, len, None); - Some(memory_offset) + interpreter.ext.charge_or_halt(RuntimeCosts::CopyToContract(len as u32))?; + let memory_offset = as_usize_or_halt::(memory_offset)?; + interpreter.memory.resize(memory_offset, len)?; + ControlFlow::Continue(Some(memory_offset)) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs index 575b82ebc6bf6..83dd1cb7e1bed 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs @@ -17,42 +17,39 @@ use crate::{ address::AddressMapper, - vm::{evm::U256Converter, RuntimeCosts}, + vm::{ + evm::{interpreter::Halt, Interpreter}, + Ext, RuntimeCosts, + }, + Config, Error, }; -use revm::primitives::Address; - -use super::Context; -use crate::{vm::Ext, Config}; +use core::ops::ControlFlow; /// Implements the GASPRICE instruction. /// /// Gets the gas price of the originating transaction. -pub fn gasprice<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::GasPrice); - push!(context.interpreter, context.interpreter.extend.effective_gas_price().into_revm_u256()); +pub fn gasprice(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::GasPrice)?; + interpreter.stack.push(interpreter.ext.effective_gas_price()) } /// Implements the ORIGIN instruction. /// /// Gets the execution origination address. -pub fn origin<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas!(context.interpreter, RuntimeCosts::Origin); - match context.interpreter.extend.origin().account_id() { +pub fn origin(interpreter: &mut Interpreter) -> ControlFlow { + interpreter.ext.charge_or_halt(RuntimeCosts::Origin)?; + match interpreter.ext.origin().account_id() { Ok(account_id) => { - let address: Address = ::AddressMapper::to_address(account_id).0.into(); - push!(context.interpreter, address.into_word().into()); - }, - Err(_) => { - context - .interpreter - .halt(revm::interpreter::InstructionResult::FatalExternalError); + let address = ::AddressMapper::to_address(account_id); + interpreter.stack.push(address) }, + Err(_) => ControlFlow::Break(Error::::ContractTrapped.into()), } } /// Implements the BLOBHASH instruction. /// /// EIP-4844: Shard Blob Transactions - gets the hash of a transaction blob. -pub fn blob_hash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); +pub fn blob_hash<'ext, E: Ext>(_interpreter: &mut Interpreter<'ext, E>) -> ControlFlow { + ControlFlow::Break(Error::::InvalidInstruction.into()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/utility.rs b/substrate/frame/revive/src/vm/evm/instructions/utility.rs index 62f77674cf4ec..d5cfbbfcdee76 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/utility.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/utility.rs @@ -15,114 +15,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -use revm::primitives::{Address, B256, U256}; - -/// Pushes an arbitrary length slice of bytes onto the stack, padding the last word with zeros -/// if necessary. -/// -/// # Panics -/// -/// Panics if slice is longer than 32 bytes. -#[inline] -pub fn cast_slice_to_u256(slice: &[u8], dest: &mut U256) { - if slice.is_empty() { - return; - } - assert!(slice.len() <= 32, "slice too long"); - - let n_words = slice.len().div_ceil(32); - - // SAFETY: Length checked above. - unsafe { - //let dst = self.data.as_mut_ptr().add(self.data.len()).cast::(); - //self.data.set_len(new_len); - let dst = dest.as_limbs_mut().as_mut_ptr(); - - let mut i = 0; - - // Write full words - let words = slice.chunks_exact(32); - let partial_last_word = words.remainder(); - for word in words { - // Note: We unroll `U256::from_be_bytes` here to write directly into the buffer, - // instead of creating a 32 byte array on the stack and then copying it over. - for l in word.rchunks_exact(8) { - dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); - i += 1; - } - } - - if partial_last_word.is_empty() { - return; - } - - // Write limbs of partial last word - let limbs = partial_last_word.rchunks_exact(8); - let partial_last_limb = limbs.remainder(); - for l in limbs { - dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap())); - i += 1; - } - - // Write partial last limb by padding with zeros - if !partial_last_limb.is_empty() { - let mut tmp = [0u8; 8]; - tmp[8 - partial_last_limb.len()..].copy_from_slice(partial_last_limb); - dst.add(i).write(u64::from_be_bytes(tmp)); - i += 1; - } - - debug_assert_eq!(i.div_ceil(4), n_words, "wrote too much"); - - // Zero out upper bytes of last word - let m = i % 4; // 32 / 8 - if m != 0 { - dst.add(i).write_bytes(0, 4 - m); - } - } -} - -/// Trait for converting types into U256 values. -pub trait IntoU256 { - /// Converts the implementing type into a U256 value. - fn into_u256(self) -> U256; -} - -impl IntoU256 for Address { - fn into_u256(self) -> U256 { - self.into_word().into_u256() - } -} - -impl IntoU256 for B256 { - fn into_u256(self) -> U256 { - U256::from_be_bytes(self.0) +use sp_core::{H160, U256}; + +/// Converts a `U256` value to a `usize`, saturating to `MAX` if the value is too large. +pub fn as_usize_saturated(v: U256) -> usize { + let x = &v.0; + if (x[1] == 0) & (x[2] == 0) & (x[3] == 0) { + usize::try_from(x[0]).unwrap_or(usize::MAX) + } else { + usize::MAX } } /// Trait for converting types into Address values. pub trait IntoAddress { /// Converts the implementing type into an Address value. - fn into_address(self) -> Address; + fn into_address(self) -> H160; } impl IntoAddress for U256 { - fn into_address(self) -> Address { - Address::from_word(B256::from(self.to_be_bytes())) - } -} - -#[cfg(test)] -mod tests { - use revm::primitives::address; - - use super::*; - - #[test] - fn test_into_u256() { - let addr = address!("0x0000000000000000000000000000000000000001"); - let u256 = addr.into_u256(); - assert_eq!(u256, U256::from(0x01)); - assert_eq!(u256.into_address(), addr); + fn into_address(self) -> H160 { + H160::from_slice(&self.to_big_endian()[12..]) } } diff --git a/substrate/frame/revive/src/vm/evm/interpreter.rs b/substrate/frame/revive/src/vm/evm/interpreter.rs new file mode 100644 index 0000000000000..be5663499ba6e --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/interpreter.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ExtBytecode; +use crate::{ + primitives::ExecReturnValue, + vm::{ + evm::{memory::Memory, stack::Stack}, + ExecResult, Ext, + }, + Config, DispatchError, Error, +}; +use alloc::vec::Vec; +use pallet_revive_uapi::ReturnFlags; + +/// EVM execution halt - either successful termination or error +#[derive(Debug, PartialEq)] +pub enum Halt { + Stop, + Return(Vec), + Revert(Vec), + Err(DispatchError), +} + +impl From> for Halt { + fn from(err: Error) -> Self { + Halt::Err(err.into()) + } +} + +impl From for ExecResult { + fn from(halt: Halt) -> Self { + match halt { + Halt::Stop => Ok(ExecReturnValue::default()), + Halt::Return(data) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data }), + Halt::Revert(data) => Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data }), + Halt::Err(err) => Err(err.into()), + } + } +} + +/// EVM interpreter state using sp_core types +#[derive(Debug)] +pub struct Interpreter<'a, E: Ext> { + /// Access to the environment + pub ext: &'a mut E, + /// The bytecode being executed + pub bytecode: ExtBytecode, + /// Input data for the current call + pub input: Vec, // TODO maybe just &'a[u8] + /// The execution stack + pub stack: Stack, + /// EVM memory + pub memory: Memory, +} + +impl<'a, E: Ext> Interpreter<'a, E> { + /// Create a new interpreter instance + pub fn new(bytecode: ExtBytecode, input: Vec, ext: &'a mut E) -> Self { + Self { ext, bytecode, input, stack: Stack::new(), memory: Memory::new() } + } +} diff --git a/substrate/frame/revive/src/vm/evm/memory.rs b/substrate/frame/revive/src/vm/evm/memory.rs new file mode 100644 index 0000000000000..15e9d0f46aa14 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/memory.rs @@ -0,0 +1,189 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{vm::evm::Halt, Config, Error}; +use alloc::vec::Vec; +use core::ops::{ControlFlow, Range}; + +/// EVM memory implementation +#[derive(Debug, Clone)] +pub struct Memory { + data: Vec, + _phantom: core::marker::PhantomData, +} + +impl Memory { + /// Create a new empty memory + pub fn new() -> Self { + Self { data: Vec::with_capacity(4 * 1024), _phantom: core::marker::PhantomData } + } + + /// Get a slice of memory for the given range + /// + /// # Panics + /// + /// Panics on out of bounds,if the range is non-empty. + pub fn slice(&self, range: Range) -> &[u8] { + if range.is_empty() { + return &[] + } + &self.data[range] + } + + /// Get a mutable slice of memory for the given range + /// + /// # Panics + /// + /// Panics on out of bounds. + pub fn slice_mut(&mut self, offset: usize, len: usize) -> &mut [u8] { + &mut self.data[offset..offset + len] + } + + /// Get the current memory size in bytes + pub fn size(&self) -> usize { + self.data.len() + } + + /// Resize memory to accommodate the given offset and length + pub fn resize(&mut self, offset: usize, len: usize) -> ControlFlow { + let current_len = self.data.len(); + let target_len = revm::interpreter::num_words(offset.saturating_add(len)) * 32; + if target_len > crate::limits::EVM_MEMORY_BYTES as usize { + log::debug!(target: crate::LOG_TARGET, "check memory bounds failed: offset={offset} target_len={target_len} current_len={current_len}"); + return ControlFlow::Break(Error::::OutOfGas.into()); + } + + if target_len > current_len { + self.data.resize(target_len, 0); + } + + ControlFlow::Continue(()) + } + + /// Set memory at the given `offset` + /// + /// # Panics + /// + /// Panics on out of bounds. + pub fn set(&mut self, offset: usize, data: &[u8]) { + if !data.is_empty() { + self.data[offset..offset + data.len()].copy_from_slice(data); + } + } + + /// 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. + /// + /// # Panics + /// + /// Panics on out of bounds. + 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 + self.slice_mut(memory_offset, len).fill(0); + return; + } + let data_end = core::cmp::min(data_offset + len, data.len()); + let data_len = data_end - data_offset; + + self.slice_mut(memory_offset, data_len) + .copy_from_slice(&data[data_offset..data_end]); + + // nullify rest of memory slots + // Safety: Memory is assumed to be valid. And it is commented where that assumption is + // made + self.slice_mut(memory_offset + data_len, len - data_len).fill(0); + } + + /// Returns a byte slice of the memory region at the given offset. + /// + /// # Panics + /// + /// Panics on out of bounds. + pub fn slice_len(&self, offset: usize, size: usize) -> &[u8] { + &self.data[offset..offset + size] + } + + /// Copy data within memory from src to dst + /// + /// # Panics + /// + /// Panics if range is out of scope of allocated memory. + pub fn copy(&mut self, dst: usize, src: usize, len: usize) { + self.data.copy_within(src..src + len, dst); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::Test; + + #[test] + fn test_memory_resize() { + let mut memory = Memory::::new(); + assert_eq!(memory.size(), 0); + + assert!(memory.resize(0, 100).is_continue()); + assert_eq!(memory.size(), 128); // Should be word-aligned (4 words * 32 bytes) + + // Resizing to smaller size should not shrink + assert!(memory.resize(0, 50).is_continue()); + assert_eq!(memory.size(), 128); // Should stay the same + } + + #[test] + fn test_set_get() { + let mut memory = Memory::::new(); + memory.data.resize(100, 0); + + let data = b"Hello, World!"; + memory.set(10, data); + + assert_eq!(memory.slice(10..10 + data.len()), data); + } + + #[test] + fn test_memory_copy() { + let mut memory = Memory::::new(); + memory.data.resize(100, 0); + + // Set some initial data + memory.set(0, b"Hello"); + memory.set(10, b"World"); + + // Copy "Hello" to position 20 + memory.copy(20, 0, 5); + + assert_eq!(memory.slice(20..25), b"Hello"); + assert_eq!(memory.slice(0..5), b"Hello"); // Original should still be there + } + + #[test] + fn test_set_data() { + let mut memory = Memory::::new(); + memory.data.resize(100, 0); + + let source_data = b"Hello World"; + + memory.set_data(5, 0, 5, source_data); + assert_eq!(memory.slice(5..10), b"Hello"); + + memory.set_data(15, 6, 5, source_data); + assert_eq!(memory.slice(15..20), b"World"); + } +} diff --git a/substrate/frame/revive/src/vm/evm/stack.rs b/substrate/frame/revive/src/vm/evm/stack.rs new file mode 100644 index 0000000000000..ff75c63c84ee4 --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/stack.rs @@ -0,0 +1,336 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Custom EVM stack implementation using sp_core::U256 + +use crate::{limits::EVM_STACK_LIMIT, vm::evm::interpreter::Halt, Config, Error}; +use alloc::vec::Vec; +use core::ops::ControlFlow; +use sp_core::{H160, H256, U256}; + +/// EVM stack implementation using sp_core types +#[derive(Debug, Clone)] +pub struct Stack { + stack: Vec, + _phantom: core::marker::PhantomData, +} + +/// A trait for converting types into an unsigned 256-bit integer (`U256`). +pub trait ToU256 { + /// Converts `self` into a `U256`. + fn to_u256(self) -> U256; +} + +impl ToU256 for U256 { + fn to_u256(self) -> U256 { + self + } +} + +impl ToU256 for u64 { + fn to_u256(self) -> U256 { + self.into() + } +} + +impl ToU256 for H160 { + fn to_u256(self) -> U256 { + U256::from_big_endian(H256::from(self).as_ref()) + } +} + +impl Stack { + /// Create a new empty stack + pub fn new() -> Self { + Self { stack: Vec::with_capacity(32), _phantom: core::marker::PhantomData } + } + + /// Push a value onto the stack + pub fn push(&mut self, value: impl ToU256) -> ControlFlow { + if self.stack.len() >= (EVM_STACK_LIMIT as usize) { + ControlFlow::Break(Error::::StackOverflow.into()) + } else { + self.stack.push(value.to_u256()); + ControlFlow::Continue(()) + } + } + + /// Get a reference to the top stack item without removing it + #[cfg(test)] + pub fn top(&self) -> Option<&U256> { + self.stack.last() + } + + /// Get the current stack size + pub fn len(&self) -> usize { + self.stack.len() + } + + /// Check if stack is empty + #[cfg(test)] + pub fn is_empty(&self) -> bool { + self.stack.is_empty() + } + + /// Pop multiple values from the stack + pub fn popn(&mut self) -> ControlFlow { + if self.stack.len() < N { + return ControlFlow::Break(Error::::StackUnderflow.into()); + } + + let mut result: [U256; N] = [U256::zero(); N]; + for i in 0..N { + match self.stack.pop() { + Some(value) => result[i] = value, + None => return ControlFlow::Break(Error::::StackUnderflow.into()), + } + } + ControlFlow::Continue(result) + } + + /// Pop multiple values and return them along with a mutable reference to the new top + /// This is used for operations that pop some values and modify the top of the stack + pub fn popn_top(&mut self) -> ControlFlow { + if self.stack.len() < N + 1 { + return ControlFlow::Break(Error::::StackUnderflow.into()); + } + + let mut popped: [U256; N] = [U256::zero(); N]; + for i in 0..N { + match self.stack.pop() { + Some(value) => popped[i] = value, + None => return ControlFlow::Break(Error::::StackUnderflow.into()), + } + } + + // Get mutable reference to the new top + match self.stack.last_mut() { + Some(top) => ControlFlow::Continue((popped, top)), + None => ControlFlow::Break(Error::::StackUnderflow.into()), + } + } + + /// Duplicate the Nth item from the top and push it onto the stack + pub fn dup(&mut self, n: usize) -> ControlFlow { + if n == 0 || n > self.stack.len() { + return ControlFlow::Break(Error::::StackUnderflow.into()); + } + if self.stack.len() >= (EVM_STACK_LIMIT as usize) { + return ControlFlow::Break(Error::::StackOverflow.into()); + } + + let idx = self.stack.len() - n; + let value = self.stack[idx]; + self.stack.push(value); + ControlFlow::Continue(()) + } + + /// Swap the top stack item with the Nth item from the top + pub fn exchange(&mut self, i: usize, j: usize) -> ControlFlow { + let len = self.stack.len(); + if i >= len || j >= len { + return ControlFlow::Break(Error::::StackUnderflow.into()); + } + + let i_idx = len - 1 - i; + let j_idx = len - 1 - j; + self.stack.swap(i_idx, j_idx); + ControlFlow::Continue(()) + } + + /// Pushes a slice of bytes onto the stack, padding the last word with zeros + /// if necessary. + /// + /// # Panics + /// + /// Panics if slice is longer than 32 bytes. + pub fn push_slice(&mut self, slice: &[u8]) -> ControlFlow { + debug_assert!(slice.len() <= 32, "slice must be at most 32 bytes"); + if slice.is_empty() { + return ControlFlow::Continue(()); + } + + if self.stack.len() >= (EVM_STACK_LIMIT as usize) { + return ControlFlow::Break(Error::::StackOverflow.into()); + } + + let mut word_bytes = [0u8; 32]; + let offset = 32 - slice.len(); + word_bytes[offset..].copy_from_slice(slice); + + self.stack.push(U256::from_big_endian(&word_bytes)); + return ControlFlow::Continue(()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::Test; + + #[test] + fn test_push_slice() { + // No-op + let mut stack = Stack::::new(); + assert!(stack.push_slice(b"").is_continue()); + assert!(stack.is_empty()); + + // Single byte + let mut stack = Stack::::new(); + assert!(stack.push_slice(&[42]).is_continue()); + assert_eq!(stack.stack, vec![U256::from(42)]); + + // 16-byte value (128-bit) + let n = 0x1111_2222_3333_4444_5555_6666_7777_8888_u128; + let mut stack = Stack::::new(); + assert!(stack.push_slice(&n.to_be_bytes()).is_continue()); + assert_eq!(stack.stack, vec![U256::from(n)]); + + // Full 32-byte value + let mut stack = Stack::::new(); + let bytes_32 = [42u8; 32]; + assert!(stack.push_slice(&bytes_32).is_continue()); + assert_eq!(stack.stack, vec![U256::from_big_endian(&bytes_32)]); + } + + #[test] + fn test_push_pop() { + let mut stack = Stack::::new(); + + // Test push + assert!(matches!(stack.push(U256::from(42)), ControlFlow::Continue(()))); + assert_eq!(stack.len(), 1); + + // Test pop + assert_eq!(stack.popn::<1>(), ControlFlow::Continue([U256::from(42)])); + assert_eq!(stack.len(), 0); + assert_eq!(stack.popn::<1>(), ControlFlow::Break(Error::::StackUnderflow.into())); + } + + #[test] + fn test_popn() { + let mut stack = Stack::::new(); + + // Push some values + for i in 1..=3 { + assert!(stack.push(U256::from(i)).is_continue()); + } + + // Pop multiple values + let result: ControlFlow<_, [U256; 2]> = stack.popn(); + assert_eq!(result, ControlFlow::Continue([U256::from(3), U256::from(2)])); + assert_eq!(stack.len(), 1); + + // Try to pop more than available + let result: ControlFlow<_, [U256; 2]> = stack.popn(); + assert_eq!(result, ControlFlow::Break(Error::::StackUnderflow.into())); + } + + #[test] + fn test_popn_top() { + let mut stack = Stack::::new(); + + // Push some values + for i in 1..=4 { + assert!(stack.push(U256::from(i)).is_continue()); + } + + // Pop 2 values and get mutable reference to new top + let result = stack.popn_top::<2>(); + assert!(matches!(result, ControlFlow::Continue(_))); + let (popped, top_ref) = match result { + ControlFlow::Continue(val) => val, + ControlFlow::Break(_) => panic!("Expected Continue"), + }; + assert_eq!(popped, [U256::from(4), U256::from(3)]); + assert_eq!(*top_ref, U256::from(2)); + + // Modify the top + *top_ref = U256::from(99); + assert_eq!(stack.top(), Some(&U256::from(99))); + } + + #[test] + fn test_dup() { + let mut stack = Stack::::new(); + + let _ = stack.push(U256::from(1)); + let _ = stack.push(U256::from(2)); + + // Duplicate the top item (index 1) + assert!(matches!(stack.dup(1), ControlFlow::Continue(()))); + assert_eq!(stack.stack, vec![U256::from(1), U256::from(2), U256::from(2)]); + + // Duplicate the second item (index 2) + assert!(matches!(stack.dup(2), ControlFlow::Continue(()))); + assert_eq!(stack.stack, vec![U256::from(1), U256::from(2), U256::from(2), U256::from(2)]); + } + + #[test] + fn test_exchange() { + let mut stack = Stack::::new(); + + let _ = stack.push(U256::from(1)); + let _ = stack.push(U256::from(2)); + let _ = stack.push(U256::from(3)); + + // Swap top (index 0) with second (index 1) + assert!(matches!(stack.exchange(0, 1), ControlFlow::Continue(()))); + assert_eq!(stack.stack, vec![U256::from(1), U256::from(3), U256::from(2)]); + } + + #[test] + fn test_stack_limit() { + let mut stack = Stack::::new(); + + // Fill stack to limit + for i in 0..EVM_STACK_LIMIT { + assert!(matches!(stack.push(U256::from(i)), ControlFlow::Continue(()))); + } + + // Should fail to push one more + assert_eq!( + stack.push(U256::from(9999)), + ControlFlow::Break(Error::::StackOverflow.into()) + ); + assert_eq!(stack.len(), EVM_STACK_LIMIT as usize); + } + + #[test] + fn test_top() { + let mut stack = Stack::::new(); + assert_eq!(stack.top(), None); + + let _ = stack.push(U256::from(42)); + assert_eq!(stack.top(), Some(&U256::from(42))); + + let _ = stack.push(U256::from(100)); + assert_eq!(stack.top(), Some(&U256::from(100))); + } + + #[test] + fn test_is_empty() { + let mut stack = Stack::::new(); + assert!(stack.is_empty()); + + assert!(stack.push(U256::from(1)).is_continue()); + assert!(!stack.is_empty()); + + assert!(stack.popn::<1>().is_continue()); + assert!(stack.is_empty()); + } +} diff --git a/substrate/frame/revive/src/vm/evm/util.rs b/substrate/frame/revive/src/vm/evm/util.rs new file mode 100644 index 0000000000000..a19084d210c2b --- /dev/null +++ b/substrate/frame/revive/src/vm/evm/util.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{vm::evm::interpreter::Halt, Config, Error, U256}; +use core::ops::ControlFlow; + +/// Helper function to convert U256 to usize, checking for overflow +pub fn as_usize_or_halt_with(value: U256, halt: impl Fn() -> Halt) -> ControlFlow { + let limbs = value.0; + if (limbs[0] > usize::MAX as u64) | (limbs[1] != 0) | (limbs[2] != 0) | (limbs[3] != 0) { + ControlFlow::Break(halt()) + } else { + ControlFlow::Continue(limbs[0] as usize) + } +} + +/// Helper function to convert U256 to usize, checking for overflow, with default OutOfGas +/// error +pub fn as_usize_or_halt(value: U256) -> ControlFlow { + as_usize_or_halt_with(value, || Error::::OutOfGas.into()) +} diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index 39efa6432dd52..6e1fc9853f590 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -345,11 +345,9 @@ impl Executable for ContractBlob { self.prepare_call(pvm::Runtime::new(ext, input_data), function, 0)?; prepared_call.call() } else if T::AllowEVMBytecode::get() { - use crate::vm::evm::EVMInputs; use revm::bytecode::Bytecode; - let inputs = EVMInputs::new(input_data); let bytecode = Bytecode::new_raw(self.code.into()); - evm::call(bytecode, ext, inputs) + evm::call(bytecode, ext, input_data) } else { Err(Error::::CodeRejected.into()) }