diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index 2dc61814ff7022a662318f62cd2ecee16ac7fc87..81be5a38f25f9a404e229f09f1691e12a5e3f4c9 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -10,6 +10,7 @@ Its storage version is 35. See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930). (!17766) - Introduces a new feature flag to enable REVM as the EVM execution engine instead of Sputnik. (!18384) +- REVM is plugged in the kernel and is guarded by a feature flag. (!18390) - The validation mechanism that automatically rejects transactions with over-estimated gas limits exceeding the maximum threshold will be disabled. During execution, any gas limit above the maximum will diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index a6f717d65c035404e43afe98992f94bb5fa14b27..75e3378d5ec34e5cdce171f3045a2be4820b4cef 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -737,9 +737,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", @@ -1189,6 +1189,8 @@ dependencies = [ "pretty_assertions", "primitive-types", "proptest", + "revm", + "revm-etherlink", "rlp", "sha3", "softfloat", diff --git a/etherlink/kernel_latest/Cargo.toml b/etherlink/kernel_latest/Cargo.toml index db5044297ac332c65a3d011119101d81e4fe59d1..291e1652cc0eba57018d6c9d9d95ecc794da559c 100644 --- a/etherlink/kernel_latest/Cargo.toml +++ b/etherlink/kernel_latest/Cargo.toml @@ -48,6 +48,7 @@ nom = { version = "7.1", default-features = false } serde = { version = "1.0", features = ["derive", "rc"] } # ethereum VM +revm = { version = "25.0.0", default-features = false } evm = { path = "../sputnikvm", default-features = false } aurora-engine-modexp = { version = "1.0", default-features = false } bn = { package = "substrate-bn", version = "0.6", default-features = false } @@ -63,6 +64,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = [ ] } # kernel crates +revm-etherlink = { package = "revm-etherlink", path = "./revm" } tezos_ethereum = { package = "tezos_ethereum_latest", path = "./ethereum" } tezos_tezlink = { package = "tezos_tezlink_latest", path = "./tezos" } evm-execution = { package = "evm-execution-latest", path = "./evm_execution" } diff --git a/etherlink/kernel_latest/kernel/Cargo.toml b/etherlink/kernel_latest/kernel/Cargo.toml index 73c553c6bc0ec4f0eb4f0f16ab7fe8ac82afa9f0..0d4b93fc1769420e32384d95140b638cfebcd484 100644 --- a/etherlink/kernel_latest/kernel/Cargo.toml +++ b/etherlink/kernel_latest/kernel/Cargo.toml @@ -39,6 +39,8 @@ ethbloom.workspace = true evm.workspace = true evm-execution.workspace = true +revm.workspace = true +revm-etherlink.workspace = true tezos-execution.workspace = true tezos_ethereum.workspace = true tezos_tezlink.workspace = true diff --git a/etherlink/kernel_latest/kernel/src/apply.rs b/etherlink/kernel_latest/kernel/src/apply.rs index 9a4baa73647ba0f7f4bb60301784cb164cc388c4..bef2a5d65890cd413274378905988f48066ab84c 100644 --- a/etherlink/kernel_latest/kernel/src/apply.rs +++ b/etherlink/kernel_latest/kernel/src/apply.rs @@ -7,7 +7,7 @@ // SPDX-License-Identifier: MIT use ethereum::Log; -use evm::Config; +use evm::{Config, ExitSucceed, Opcode}; use evm_execution::account_storage::{EthereumAccount, EthereumAccountStorage}; use evm_execution::fa_bridge::deposit::FaDeposit; use evm_execution::fa_bridge::queue_fa_deposit; @@ -24,6 +24,8 @@ use evm_execution::trace::{ get_tracer_configuration, CallTrace, CallTracerConfig, CallTracerInput, TracerInput, }; use primitive_types::{H160, H256, U256}; +use std::borrow::Cow; +use tezos_ethereum::access_list::{AccessList, AccessListItem}; use tezos_ethereum::block::BlockConstants; use tezos_ethereum::transaction::{TransactionHash, TransactionType}; use tezos_ethereum::tx_common::EthereumTransactionCommon; @@ -37,6 +39,7 @@ use crate::bridge::{execute_deposit, Deposit}; use crate::chains::EvmLimits; use crate::error::Error; use crate::fees::{tx_execution_gas_limit, FeeUpdates}; +use crate::storage::is_revm_enabled; use crate::transaction::{Transaction, TransactionContent}; // This implementation of `Transaction` is used to share the logic of @@ -301,6 +304,212 @@ fn log_transaction_type(host: &Host, to: Option, data: &[u8 } } +#[allow(clippy::too_many_arguments)] +fn revm_run_transaction( + host: &mut Host, + block_constants: &BlockConstants, + caller: H160, + to: Option, + value: U256, + gas_limit: u64, + call_data: Vec, + effective_gas_price: U256, + access_list: AccessList, +) -> Result, anyhow::Error> { + // Disclaimer: + // The following code is over-complicated because we maintain + // two sets of primitives inside the kernel's codebase. + // There's a lot of dummy conversions that are + // needed to make the translation from our type to the + // ones from REVM (and the other way round). + // + // NB: + // None of the revm primitives are imported globally on purpose to + // avoid disturbing the workflow of other engineers. This part of the + // code is extremely self-contained on purpose. + // + // TODO: Simplify all the base structures to avoid these translations + // once we fully make the switch to REVM. + let mut world_state_handler = + match revm_etherlink::world_state_handler::new_world_state_handler() { + Ok(world_state_handler) => world_state_handler, + Err(err) => { + return Err(Error::InvalidRunTransaction( + evm_execution::EthereumError::WrappedError(Cow::from(format!( + "Failed to initialize world state handler: {err:?}" + ))), + ) + .into()) + } + }; + match revm_etherlink::run_transaction( + host, + block_constants, + &mut world_state_handler, + revm_etherlink::precompile_provider::EtherlinkPrecompiles::new(), + revm::primitives::Address::from_slice(&caller.0), + to.map(|to| revm::primitives::Address::from_slice(&to.0)), + revm::primitives::Bytes::from(call_data), + Some(gas_limit), + u128::from_le_bytes(if effective_gas_price.bits() < 128 { + effective_gas_price.low_u128().to_le_bytes() + } else { + return Err(Error::InvalidRunTransaction( + evm_execution::EthereumError::WrappedError(Cow::from( + "Given amount does not fit in a u128", + )), + ) + .into()); + }), + revm::primitives::U256::from_le_slice( + &(evm_execution::utilities::u256_to_le_bytes(value)), + ), + revm::context::transaction::AccessList::from( + access_list + .into_iter() + .map( + |AccessListItem { + address, + storage_keys, + }| { + revm::context::transaction::AccessListItem { + address: revm::primitives::Address::from_slice(&address.0), + storage_keys: storage_keys + .into_iter() + .map(|key| revm::primitives::B256::from_slice(&key.0)) + .collect(), + } + }, + ) + .collect::>(), + ), + ) { + Ok(outcome) => { + let gas_used = outcome.result.gas_used(); + let logs = outcome + .result + .logs() + .iter() + .map(|revm::primitives::Log { address, data }| Log { + address: H160(***address), + topics: data.topics().iter().map(|topic| H256(**topic)).collect(), + data: data.data.to_vec(), + }) + .collect(); + let result = match outcome.result { + revm::context::result::ExecutionResult::Success { output: revm::context::result::Output::Call(output), .. } => { + evm_execution::handler::ExecutionResult::CallSucceeded(ExitSucceed::Returned, output.to_vec()) + }, + revm::context::result::ExecutionResult::Success { output: revm::context::result::Output::Create(bytecode,address), .. } => { + evm_execution::handler::ExecutionResult::ContractDeployed(H160(**address.unwrap_or_default()), bytecode.to_vec()) + }, + revm::context::result::ExecutionResult::Revert { output, .. } => { + evm_execution::handler::ExecutionResult::CallReverted(output.to_vec()) + }, + revm::context::result::ExecutionResult::Halt { reason, .. } => match reason { + revm::context::result::HaltReason::OutOfGas(_) => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::OutOfGas) + }, + revm::context::result::HaltReason::OpcodeNotFound => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("OpcodeNotFound"))) + }, + revm::context::result::HaltReason::InvalidFEOpcode => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::InvalidCode(Opcode(0xfe))) + }, + revm::context::result::HaltReason::InvalidJump => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::InvalidJump) + }, + revm::context::result::HaltReason::NotActivated => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("NotActivated"))) + }, + revm::context::result::HaltReason::StackUnderflow => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::StackUnderflow) + }, + revm::context::result::HaltReason::StackOverflow => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::StackOverflow) + }, + revm::context::result::HaltReason::OutOfOffset => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("OutOfOffset"))) + }, + revm::context::result::HaltReason::CreateCollision => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::CreateCollision) + }, + revm::context::result::HaltReason::PrecompileError => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("PrecompileError"))) + }, + revm::context::result::HaltReason::NonceOverflow => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::MaxNonce) + }, + revm::context::result::HaltReason::CreateContractSizeLimit => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::CreateContractLimit) + }, + revm::context::result::HaltReason::CreateContractStartingWithEF => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::InvalidCode(Opcode(0xef))) + }, + revm::context::result::HaltReason::CreateInitCodeSizeLimit => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("CreateInitCodeSizeLimit"))) + }, + revm::context::result::HaltReason::OverflowPayment => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("OverflowPayment"))) + }, + revm::context::result::HaltReason::StateChangeDuringStaticCall => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("StateChangeDuringStaticCall"))) + }, + revm::context::result::HaltReason::CallNotAllowedInsideStatic => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("CallNotAllowedInsideStatic"))) + }, + revm::context::result::HaltReason::OutOfFunds => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::OutOfFund) + }, + revm::context::result::HaltReason::CallTooDeep => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::CallTooDeep) + }, + revm::context::result::HaltReason::EofAuxDataOverflow => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("EofAuxDataOverflow"))) + }, + revm::context::result::HaltReason::EofAuxDataTooSmall => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("EofAuxDataTooSmall"))) + }, + revm::context::result::HaltReason::SubRoutineStackOverflow => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::StackOverflow) + }, + revm::context::result::HaltReason::InvalidEXTCALLTarget => { + evm_execution::handler::ExecutionResult::Error(evm::ExitError::Other(Cow::from("InvalidEXTCALLTarget"))) + }, + }, + }; + Ok(Some(ExecutionOutcome { + gas_used, + logs, + result, + withdrawals: outcome + .withdrawals + .into_iter() + .map(|withdrawal| match withdrawal { + revm_etherlink::send_outbox_message::Withdrawal::Standard( + outbox_message_full, + ) => evm_execution::handler::Withdrawal::Standard( + outbox_message_full, + ), + revm_etherlink::send_outbox_message::Withdrawal::Fast( + outbox_message_full, + ) => { + evm_execution::handler::Withdrawal::Fast(outbox_message_full) + } + }) + .collect(), + estimated_ticks_used: 0, + })) + } + Err(err) => Err(Error::InvalidRunTransaction( + evm_execution::EthereumError::WrappedError(Cow::from(format!( + "REVM error {err:?}" + ))), + ) + .into()), + } +} + #[allow(clippy::too_many_arguments)] fn apply_ethereum_transaction_common( host: &mut Host, @@ -334,25 +543,39 @@ fn apply_ethereum_transaction_common( let call_data = transaction.data.clone(); log_transaction_type(host, to, &call_data); let value = transaction.value; - let execution_outcome = match run_transaction( - host, - block_constants, - evm_account_storage, - precompiles, - evm_configuration, - to, - caller, - call_data, - Some(gas_limit), - effective_gas_price, - value, - true, - tracer_input, - transaction.access_list.clone(), - ) { - Ok(outcome) => outcome, - Err(err) => { - return Err(Error::InvalidRunTransaction(err).into()); + let execution_outcome = if is_revm_enabled(host)? { + revm_run_transaction( + host, + block_constants, + caller, + to, + value, + gas_limit, + call_data, + effective_gas_price, + transaction.access_list.clone(), + )? + } else { + match run_transaction( + host, + block_constants, + evm_account_storage, + precompiles, + evm_configuration, + to, + caller, + call_data, + Some(gas_limit), + effective_gas_price, + value, + true, + tracer_input, + transaction.access_list.clone(), + ) { + Ok(outcome) => outcome, + Err(err) => { + return Err(Error::InvalidRunTransaction(err).into()); + } } }; diff --git a/etherlink/kernel_latest/revm/Cargo.toml b/etherlink/kernel_latest/revm/Cargo.toml index 303d52c11ebb3bd88b2d31a2a850a3ccb639f38f..60430f1b4791b11b5b838c6b8a55f3a9673913b9 100644 --- a/etherlink/kernel_latest/revm/Cargo.toml +++ b/etherlink/kernel_latest/revm/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" [dependencies] # VM -revm = { version = "25.0.0", default-features = false } +revm.workspace = true # Types primitive-types.workspace = true diff --git a/etherlink/kernel_latest/revm/src/lib.rs b/etherlink/kernel_latest/revm/src/lib.rs index 2fe5a8b3c6376545d0e2635818dda7a9003d0df2..245b9d727d66a3860f6d6aedebdc45be943d3262 100644 --- a/etherlink/kernel_latest/revm/src/lib.rs +++ b/etherlink/kernel_latest/revm/src/lib.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: MIT +use crate::{database::PrecompileDatabase, send_outbox_message::Withdrawal}; use database::EtherlinkVMDB; use precompile_provider::EtherlinkPrecompiles; use revm::context::result::EVMError; @@ -15,7 +16,7 @@ use revm::{ handler::{instructions::EthInstructions, EthPrecompiles}, interpreter::interpreter::EthInterpreter, primitives::{hardfork::SpecId, Address, Bytes, FixedBytes, TxKind, U256}, - Context, ExecuteCommitEvm, InspectEvm, Journal, MainBuilder, + Context, ExecuteCommitEvm, Journal, MainBuilder, }; use storage_helpers::u256_to_le_bytes; use tezos_ethereum::block::BlockConstants; @@ -24,15 +25,14 @@ use tezos_smart_rollup_host::runtime::RuntimeError; use thiserror::Error; use world_state_handler::{account_path, WorldStateHandler}; -use crate::{database::PrecompileDatabase, send_outbox_message::Withdrawal}; +pub mod precompile_provider; +pub mod send_outbox_message; +pub mod world_state_handler; mod block_storage; mod code_storage; mod database; -mod precompile_provider; -mod send_outbox_message; mod storage_helpers; -mod world_state_handler; const ETHERLINK_CHAIN_ID: u64 = 42793; const DEFAULT_SPEC_ID: SpecId = SpecId::PRAGUE; @@ -172,11 +172,8 @@ pub fn run_transaction<'a, Host: Runtime>( gas_limit: Option, effective_gas_price: u128, value: U256, - tracer: Option, access_list: AccessList, ) -> Result> { - let _ignore_tracer = tracer; - let mut commit_status = true; let block_env = block_env(block_constants)?; let tx = tx_env( diff --git a/etherlink/kernel_latest/revm/src/send_outbox_message.rs b/etherlink/kernel_latest/revm/src/send_outbox_message.rs index 0cb49a9ff92faee102d27ad46e8fd7088ecafc84..db8956d298d5d45e3757e99a681c337b4510f979 100644 --- a/etherlink/kernel_latest/revm/src/send_outbox_message.rs +++ b/etherlink/kernel_latest/revm/src/send_outbox_message.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-FileCopyrightText: 2025 Functori // // SPDX-License-Identifier: MIT @@ -61,7 +62,6 @@ pub type FastWithdrawalInterface = MichelsonPair< /// Outbox messages that implements the different withdrawal interfaces, /// ready to be encoded and posted. -#[allow(dead_code)] #[derive(Debug, PartialEq, Eq)] pub enum Withdrawal { Standard(OutboxMessage), diff --git a/etherlink/kernel_latest/revm/src/world_state_handler.rs b/etherlink/kernel_latest/revm/src/world_state_handler.rs index ab44b9f92616e0bd56fefbec17aeb58326ef1cf6..c3ab654769dd780d80beee5fb586801d292bb0c5 100644 --- a/etherlink/kernel_latest/revm/src/world_state_handler.rs +++ b/etherlink/kernel_latest/revm/src/world_state_handler.rs @@ -22,7 +22,6 @@ use crate::{ Error, }; -#[cfg(test)] /// Path where EVM accounts are stored. pub const EVM_ACCOUNTS_PATH: RefPath = RefPath::assert_from(b"/evm/world_state/eth_accounts"); @@ -224,7 +223,6 @@ impl From for StorageAccount { pub type WorldStateHandler = Storage; -#[cfg(test)] pub fn new_world_state_handler() -> Result { Storage::::init(&EVM_ACCOUNTS_PATH) .map_err(|err| Error::Custom(err.to_string()))