diff --git a/Cargo.lock b/Cargo.lock index a681de9ee..46f238a6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6146,6 +6146,7 @@ dependencies = [ "portalnet", "reth-ipc", "reth-rpc-types", + "revm", "serde", "serde_json", "strum", @@ -6154,6 +6155,7 @@ dependencies = [ "tower", "tower-http", "tracing", + "trin-execution", "trin-utils", "trin-validation", ] @@ -8141,6 +8143,7 @@ dependencies = [ "parking_lot 0.11.2", "prometheus_exporter", "rayon", + "reth-rpc-types", "revm", "revm-inspectors", "revm-primitives", diff --git a/Cargo.toml b/Cargo.toml index 0b322a407..a6e14eae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,8 +112,8 @@ r2d2_sqlite = "0.24.0" rand = "0.8.5" reth-ipc = { tag = "v0.2.0-beta.5", git = "https://github.com/paradigmxyz/reth.git"} reth-rpc-types = { tag = "v1.0.6", git = "https://github.com/paradigmxyz/reth.git"} -revm = { version = "14.0.1", features = ["std", "secp256k1", "serde-json"], default-features = false } -revm-primitives = { version = "9.0.1", features = ["std", "serde"], default-features = false } +revm = { version = "14.0.1", default-features = false, features = ["std", "secp256k1", "serde-json", "optional_block_gas_limit"] } +revm-primitives = { version = "9.0.1", default-features = false, features = ["std", "serde"] } rpc = { path = "rpc"} rstest = "0.18.2" rusqlite = { version = "0.31.0", features = ["bundled"] } diff --git a/ethportal-api/src/eth.rs b/ethportal-api/src/eth.rs index e03679768..1a91898ed 100644 --- a/ethportal-api/src/eth.rs +++ b/ethportal-api/src/eth.rs @@ -1,6 +1,6 @@ use alloy_primitives::{Address, Bytes, B256, U256}; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -use reth_rpc_types::{Block, BlockId}; +use reth_rpc_types::{Block, BlockId, TransactionRequest}; /// Web3 JSON-RPC endpoints #[rpc(client, server, namespace = "eth")] @@ -24,4 +24,7 @@ pub trait EthApi { #[method(name = "getStorageAt")] async fn get_storage_at(&self, address: Address, slot: U256, block: BlockId) -> RpcResult; + + #[method(name = "call")] + async fn call(&self, transaction: TransactionRequest, block: BlockId) -> RpcResult; } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index d62abdb7b..32231c41a 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -20,6 +20,7 @@ hyper.workspace = true portalnet.workspace = true reth-ipc.workspace = true reth-rpc-types.workspace = true +revm.workspace = true serde.workspace = true serde_json.workspace = true strum.workspace = true @@ -28,5 +29,6 @@ tokio.workspace = true tower = { version = "0.4.13", features = ["full"] } tower-http = { version = "0.4.4", features = ["full"] } tracing.workspace = true +trin-execution.workspace = true trin-utils.workspace = true trin-validation.workspace = true diff --git a/rpc/src/errors.rs b/rpc/src/errors.rs index 2353d6059..44f7edaa1 100644 --- a/rpc/src/errors.rs +++ b/rpc/src/errors.rs @@ -53,12 +53,16 @@ impl RpcError { } } +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum RpcServeError { /// A generic error with no data + #[error("Error: {0}")] Message(String), /// Method not available + #[error("Method not available: {0}")] MethodNotFound(String), /// ContentNotFound + #[error("Content not found: {message}")] ContentNotFound { message: String, trace: Option>, diff --git a/rpc/src/eth_rpc.rs b/rpc/src/eth_rpc.rs index 3145edc63..1e5060b52 100644 --- a/rpc/src/eth_rpc.rs +++ b/rpc/src/eth_rpc.rs @@ -1,31 +1,27 @@ -use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; -use alloy_rlp::Decodable; -use eth_trie::node::Node; -use reth_rpc_types::{Block, BlockId, BlockTransactions}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use reth_rpc_types::{Block, BlockId, BlockTransactions, TransactionRequest}; use tokio::sync::mpsc; use ethportal_api::{ types::{ - content_key::state::{AccountTrieNodeKey, ContractBytecodeKey, ContractStorageTrieNodeKey}, execution::block_body::BlockBody, jsonrpc::{ - endpoints::{HistoryEndpoint, StateEndpoint}, + endpoints::HistoryEndpoint, request::{HistoryJsonRpcRequest, StateJsonRpcRequest}, }, portal::ContentInfo, - state_trie::{ - account_state::AccountState, - nibbles::Nibbles, - trie_traversal::{NodeTraversal, TraversalResult}, - }, }, - ContentValue, EthApiServer, Header, HistoryContentKey, HistoryContentValue, StateContentKey, - StateContentValue, + ContentValue, EthApiServer, Header, HistoryContentKey, HistoryContentValue, +}; +use trin_execution::evm::{ + async_db::{execute_transaction_with_evm_modifier, AsyncDatabase}, + create_block_env, }; use trin_validation::constants::CHAIN_ID; use crate::{ errors::RpcServeError, + evm_state::EvmBlockState, fetch::proxy_to_subnet, jsonrpsee::core::{async_trait, RpcResult}, }; @@ -91,57 +87,63 @@ impl EthApiServer for EthApi { } async fn get_balance(&self, address: Address, block: BlockId) -> RpcResult { - let address_hash = keccak256(address); - let block_hash = as_block_hash(block)?; - let header = self.fetch_header_by_hash(block_hash).await?; - - let account_state = self - .fetch_account_state(header.state_root, address_hash) - .await?; - - match account_state { - Some(account_state) => Ok(account_state.balance), - None => Ok(U256::ZERO), - } - } - - async fn get_code(&self, address: Address, block: BlockId) -> RpcResult { - let address_hash = keccak256(address); - let block_hash = as_block_hash(block)?; - let header = self.fetch_header_by_hash(block_hash).await?; - - let account_state = self - .fetch_account_state(header.state_root, address_hash) - .await?; + let mut evm_block_state = self.evm_block_state(block).await?; - match account_state { - Some(account_state) => Ok(self - .fetch_contract_bytecode(address_hash, account_state.code_hash) - .await?), - None => Ok(Bytes::new()), - } + let Some(account_info) = evm_block_state.basic_async(address).await? else { + return Ok(U256::ZERO); + }; + Ok(account_info.balance) } - async fn get_storage_at( &self, address: Address, slot: U256, block: BlockId, ) -> RpcResult { - let address_hash = keccak256(address); - let block_hash = as_block_hash(block)?; - let header = self.fetch_header_by_hash(block_hash).await?; + let mut evm_block_state = self.evm_block_state(block).await?; - let account_state = self - .fetch_account_state(header.state_root, address_hash) - .await?; + let value = evm_block_state.storage_async(address, slot).await?; + Ok(B256::from(value)) + } + + async fn get_code(&self, address: Address, block: BlockId) -> RpcResult { + let mut evm_block_state = self.evm_block_state(block).await?; + + let Some(account_info) = evm_block_state.basic_async(address).await? else { + return Ok(Bytes::new()); + }; - match account_state { - Some(account_state) => Ok(self - .fetch_contract_storage_at_slot(account_state.storage_root, address_hash, slot) - .await?), - None => Ok(B256::ZERO), + if account_info.is_empty_code_hash() { + return Ok(Bytes::new()); } + + let bytecode = match account_info.code { + Some(code) => code, + None => { + evm_block_state + .code_by_hash_async(account_info.code_hash()) + .await? + } + }; + Ok(bytecode.original_bytes()) + } + + async fn call(&self, transaction: TransactionRequest, block: BlockId) -> RpcResult { + let evm_block_state = self.evm_block_state(block).await?; + + let result_and_state = execute_transaction_with_evm_modifier( + create_block_env(evm_block_state.block_header()), + transaction, + evm_block_state, + |evm| { + // Allow unlimited gas + evm.cfg_mut().disable_block_gas_limit = true; + }, + ) + .await + .map_err(|err| RpcServeError::Message(err.to_string()))?; + let output = result_and_state.result.into_output().unwrap_or_default(); + Ok(output) } } @@ -190,144 +192,15 @@ impl EthApi { // State network related functions - async fn fetch_state_content( - &self, - content_key: StateContentKey, - ) -> Result { + async fn evm_block_state(&self, block: BlockId) -> Result { let Some(state_network) = &self.state_network else { - return Err(RpcServeError::Message(format!( - "State network not enabled. Can't find: {content_key}" - ))); - }; - - let endpoint = StateEndpoint::RecursiveFindContent(content_key.clone()); - let response: ContentInfo = proxy_to_subnet(state_network, endpoint).await?; - let ContentInfo::Content { content, .. } = response else { - return Err(RpcServeError::Message(format!( - "Invalid response variant: State RecursiveFindContent should contain content value; got {response:?}" - ))); - }; - - let content_value = StateContentValue::decode(&content_key, &content)?; - Ok(content_value) - } - - async fn fetch_trie_node(&self, content_key: StateContentKey) -> Result { - let content_value = self.fetch_state_content(content_key).await?; - let StateContentValue::TrieNode(trie_node) = content_value else { - return Err(RpcServeError::Message(format!( - "Invalid response: expected trie node; got {content_value:?}", - ))); - }; - trie_node - .node - .as_trie_node() - .map_err(|err| RpcServeError::Message(format!("Can't decode trie_node: {err}"))) - } - - async fn fetch_contract_bytecode( - &self, - address_hash: B256, - code_hash: B256, - ) -> Result { - let content_key = StateContentKey::ContractBytecode(ContractBytecodeKey { - address_hash, - code_hash, - }); - let content_value = self.fetch_state_content(content_key).await?; - let StateContentValue::ContractBytecode(contract_bytecode) = content_value else { - return Err(RpcServeError::Message(format!( - "Invalid response: expected contract bytecode; got {content_value:?}", - ))); - }; - let bytes = Vec::from(contract_bytecode.code); - Ok(Bytes::from(bytes)) - } - - async fn fetch_account_state( - &self, - state_root: B256, - address_hash: B256, - ) -> Result, RpcServeError> { - self.traverse_trie(state_root, address_hash, |path, node_hash| { - StateContentKey::AccountTrieNode(AccountTrieNodeKey { path, node_hash }) - }) - .await - } - - async fn fetch_contract_storage_at_slot( - &self, - storage_root: B256, - address_hash: B256, - storage_slot: U256, - ) -> Result { - let path = keccak256(storage_slot.to_be_bytes::<32>()); - let value = self - .traverse_trie::(storage_root, path, |path, node_hash| { - StateContentKey::ContractStorageTrieNode(ContractStorageTrieNodeKey { - address_hash, - path, - node_hash, - }) - }) - .await? - .unwrap_or_default(); - if value.len() > B256::len_bytes() { - return Err(RpcServeError::Message(format!( - "Storage value is too long. value: {value}" - ))); - } - - Ok(B256::left_padding_from(&value)) - } - - /// Utility function for fetching trie nodes and traversing the trie. - /// - /// This function works both with the account trie and the contract state trie. - async fn traverse_trie( - &self, - root: B256, - path: B256, - content_key_fn: impl Fn(Nibbles, B256) -> StateContentKey, - ) -> Result, RpcServeError> { - let path = Nibbles::unpack_nibbles(path.as_slice()); - - let mut node_hash = root; - let mut remaining_path = path.as_slice(); - - let value = loop { - let path_from_root = path - .strip_suffix(remaining_path) - .expect("Remaining path should be suffix of the path"); - - let content_key = content_key_fn( - Nibbles::try_from_unpacked_nibbles(path_from_root) - .expect("we should be able to create Nibbles from path"), - node_hash, - ); - let node = self.fetch_trie_node(content_key).await?; - - match node.traverse(remaining_path) { - TraversalResult::Empty(_) => break None, - TraversalResult::Value(value) => break Some(value), - TraversalResult::Node(next_node) => { - node_hash = next_node.hash; - remaining_path = next_node.remaining_path; - } - TraversalResult::Error(err) => { - return Err(RpcServeError::Message(format!( - "Error traversing trie node: {err}" - ))) - } - } + return Err(RpcServeError::Message( + "State network not enabled. Can't process request!".to_string(), + )); }; - - value - .map(|value| T::decode(&mut value.as_ref())) - .transpose() - .map_err(|err| { - RpcServeError::Message(format!("Error decoding value from the leaf node: {err}")) - }) + let block_hash = as_block_hash(block)?; + let header = self.fetch_header_by_hash(block_hash).await?; + Ok(EvmBlockState::new(header, state_network.clone())) } } diff --git a/rpc/src/evm_state.rs b/rpc/src/evm_state.rs new file mode 100644 index 000000000..b9c48bffc --- /dev/null +++ b/rpc/src/evm_state.rs @@ -0,0 +1,258 @@ +use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; +use alloy_rlp::Decodable; +use eth_trie::{node::Node, TrieError}; +use ethportal_api::{ + jsonrpsee::types::ErrorObjectOwned, + types::{ + content_key::state::{AccountTrieNodeKey, ContractBytecodeKey, ContractStorageTrieNodeKey}, + jsonrpc::{endpoints::StateEndpoint, request::StateJsonRpcRequest}, + portal::ContentInfo, + state_trie::{ + account_state::AccountState, + nibbles::Nibbles, + trie_traversal::{NodeTraversal, TraversalError, TraversalResult}, + }, + }, + ContentValue, ContentValueError, Header, StateContentKey, StateContentValue, +}; +use revm::primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}; +use tokio::sync::mpsc; +use trin_execution::evm::async_db::AsyncDatabase; + +use crate::{errors::RpcServeError, fetch::proxy_to_subnet}; + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum EvmStateError { + #[error("Received content value has unexpected type. key: {0} value: {0}")] + InvalidContentValueType(StateContentKey, StateContentValue), + #[error("Error decoding content value: {0}")] + DecodingContentValue(#[from] ContentValueError), + #[error("Error decoding trie node: {0}")] + DecodingTrieNode(#[from] TrieError), + #[error("Error RLP decoding trie value: {0}")] + DecodingTrieValue(#[from] alloy_rlp::Error), + #[error("Error traversing trie: {0}")] + TrieTraversal(#[from] TraversalError), + #[error("Storage value is invalid: {0}")] + InvalidStorageValue(Bytes), + #[error("Internal Error: {0}")] + InternalError(String), +} + +impl From for RpcServeError { + fn from(value: EvmStateError) -> Self { + Self::Message(value.to_string()) + } +} + +impl From for ErrorObjectOwned { + fn from(value: EvmStateError) -> Self { + RpcServeError::from(value).into() + } +} + +/// Provides access to the EVM state at the specific block +pub struct EvmBlockState { + block_header: Header, + state_network: mpsc::UnboundedSender, +} + +impl EvmBlockState { + pub fn new( + block_header: Header, + state_network: mpsc::UnboundedSender, + ) -> Self { + Self { + block_header, + state_network, + } + } + + // Public functions + + pub fn block_header(&self) -> &Header { + &self.block_header + } + + pub async fn account_state( + &self, + address_hash: B256, + ) -> Result, EvmStateError> { + self.traverse_trie( + self.block_header.state_root, + address_hash, + |path, node_hash| { + StateContentKey::AccountTrieNode(AccountTrieNodeKey { path, node_hash }) + }, + ) + .await + } + + pub async fn contract_storage_at_slot( + &self, + storage_root: B256, + address_hash: B256, + storage_slot: U256, + ) -> Result { + let path = keccak256(storage_slot.to_be_bytes::<32>()); + let value = self + .traverse_trie::(storage_root, path, |path, node_hash| { + StateContentKey::ContractStorageTrieNode(ContractStorageTrieNodeKey { + address_hash, + path, + node_hash, + }) + }) + .await? + .unwrap_or_default(); + if value.len() > B256::len_bytes() { + return Err(EvmStateError::InvalidStorageValue(value)); + } + + Ok(B256::left_padding_from(&value)) + } + + pub async fn contract_bytecode( + &self, + address_hash: B256, + code_hash: B256, + ) -> Result { + let content_key = StateContentKey::ContractBytecode(ContractBytecodeKey { + address_hash, + code_hash, + }); + let content_value = self.fetch_content(content_key.clone()).await?; + let StateContentValue::ContractBytecode(contract_bytecode) = content_value else { + return Err(EvmStateError::InvalidContentValueType( + content_key, + content_value, + )); + }; + let bytes = Vec::from(contract_bytecode.code); + Ok(Bytes::from(bytes)) + } + + // Utility functions + + async fn fetch_content( + &self, + content_key: StateContentKey, + ) -> Result { + let endpoint = StateEndpoint::RecursiveFindContent(content_key.clone()); + let response: ContentInfo = proxy_to_subnet(&self.state_network, endpoint) + .await + .map_err(|err| EvmStateError::InternalError(err.to_string()))?; + let ContentInfo::Content { content, .. } = response else { + return Err(EvmStateError::InternalError(format!( + "Invalid response variant: State RecursiveFindContent should contain content value; got {response:?}" + ))); + }; + + let content_value = StateContentValue::decode(&content_key, &content)?; + Ok(content_value) + } + + async fn fetch_trie_node(&self, content_key: StateContentKey) -> Result { + let content_value = self.fetch_content(content_key.clone()).await?; + let StateContentValue::TrieNode(trie_node) = content_value else { + return Err(EvmStateError::InvalidContentValueType( + content_key, + content_value, + )); + }; + Ok(trie_node.node.as_trie_node()?) + } + + /// Utility function for fetching trie nodes and traversing the trie. + /// + /// This function works both with the account trie and the contract state trie. + async fn traverse_trie( + &self, + root: B256, + path: B256, + content_key_fn: impl Fn(Nibbles, B256) -> StateContentKey, + ) -> Result, EvmStateError> { + let path = Nibbles::unpack_nibbles(path.as_slice()); + + let mut node_hash = root; + let mut remaining_path = path.as_slice(); + + let value = loop { + let path_from_root = path + .strip_suffix(remaining_path) + .expect("Remaining path should be suffix of the path"); + + let content_key = content_key_fn( + Nibbles::try_from_unpacked_nibbles(path_from_root) + .expect("we should be able to create Nibbles from path"), + node_hash, + ); + let node = self.fetch_trie_node(content_key).await?; + + match node.traverse(remaining_path) { + TraversalResult::Empty(_) => break None, + TraversalResult::Value(value) => break Some(value), + TraversalResult::Node(next_node) => { + node_hash = next_node.hash; + remaining_path = next_node.remaining_path; + } + TraversalResult::Error(err) => return Err(err.into()), + } + }; + + Ok(value + .map(|value| T::decode(&mut value.as_ref())) + .transpose()?) + } +} + +impl AsyncDatabase for EvmBlockState { + type Error = EvmStateError; + + async fn basic_async(&mut self, address: Address) -> Result, Self::Error> { + let address_hash = keccak256(address); + let account_state = self.account_state(address_hash).await?; + + let Some(account_state) = account_state else { + return Ok(None); + }; + + let code = if account_state.code_hash == KECCAK_EMPTY { + Bytecode::new() + } else { + let bytecode_raw = self + .contract_bytecode(address_hash, account_state.code_hash) + .await?; + Bytecode::new_raw(bytecode_raw) + }; + Ok(Some(AccountInfo::new( + account_state.balance, + account_state.nonce, + account_state.code_hash, + code, + ))) + } + + async fn code_by_hash_async(&mut self, _code_hash: B256) -> Result { + Err(EvmStateError::InternalError( + "The code_by_hash_async is not supported".to_string(), + )) + } + + async fn storage_async(&mut self, address: Address, index: U256) -> Result { + let address_hash = keccak256(address); + let account_state = self.account_state(address_hash).await?; + let Some(account_state) = account_state else { + return Ok(U256::ZERO); + }; + self.contract_storage_at_slot(account_state.storage_root, address_hash, index) + .await + .map(|value| U256::from_be_bytes(value.0)) + } + + async fn block_hash_async(&mut self, _number: u64) -> Result { + Err(EvmStateError::InternalError( + "The block_hash_async is not supported".to_string(), + )) + } +} diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 0ab80fe21..f8826b0b1 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -7,6 +7,7 @@ mod cors; mod discv5_rpc; mod errors; mod eth_rpc; +mod evm_state; mod fetch; mod history_rpc; mod rpc_server; diff --git a/trin-execution/Cargo.toml b/trin-execution/Cargo.toml index 1ad0a6773..920fbeae3 100644 --- a/trin-execution/Cargo.toml +++ b/trin-execution/Cargo.toml @@ -29,6 +29,7 @@ rayon = "1.10.0" revm.workspace = true revm-inspectors = "0.6" revm-primitives.workspace = true +reth-rpc-types.workspace = true rocksdb = "0.22.0" serde = { workspace = true, features = ["rc"] } serde_json.workspace = true diff --git a/trin-execution/src/evm/async_db.rs b/trin-execution/src/evm/async_db.rs index 03c7cbac9..5ff14d70f 100644 --- a/trin-execution/src/evm/async_db.rs +++ b/trin-execution/src/evm/async_db.rs @@ -1,6 +1,6 @@ use std::future::Future; -use revm::Database; +use revm::{Database, Evm}; use revm_primitives::{AccountInfo, Address, BlockEnv, Bytecode, EVMError, EVMResult, B256, U256}; use tokio::{runtime, task}; @@ -40,7 +40,7 @@ pub trait AsyncDatabase { /// Wraps the [AsyncDatabase] to provide [revm::Database] implementation. /// /// This should only be used when blocking thread is allowed, e.g. from within spawn::blocking. -pub(super) struct WrapAsyncDatabase { +pub struct WrapAsyncDatabase { db: DB, rt: runtime::Runtime, } @@ -76,6 +76,20 @@ pub async fn execute_transaction( tx: Tx, db: DB, ) -> EVMResult +where + DB: AsyncDatabase + Send + 'static, + DBError: Send + 'static, + Tx: TxEnvModifier + Send + 'static, +{ + execute_transaction_with_evm_modifier(block_env, tx, db, |_| {}).await +} + +pub async fn execute_transaction_with_evm_modifier( + block_env: BlockEnv, + tx: Tx, + db: DB, + evm_modifier: impl FnOnce(&mut Evm<'_, (), &mut WrapAsyncDatabase>) + Send + 'static, +) -> EVMResult where DB: AsyncDatabase + Send + 'static, DBError: Send + 'static, @@ -85,6 +99,7 @@ where let rt = runtime::Runtime::new().expect("to create Runtime within spawn_blocking"); let mut db = WrapAsyncDatabase::new(db, rt); let mut evm = create_evm(block_env, &tx, &mut db); + evm_modifier(&mut evm); evm.transact() }) .await diff --git a/trin-execution/src/evm/tx_env_modifier.rs b/trin-execution/src/evm/tx_env_modifier.rs index 22308533a..1edc30e3b 100644 --- a/trin-execution/src/evm/tx_env_modifier.rs +++ b/trin-execution/src/evm/tx_env_modifier.rs @@ -3,6 +3,7 @@ use ethportal_api::types::execution::transaction::{ AccessListTransaction, BlobTransaction, EIP1559Transaction, LegacyTransaction, ToAddress, Transaction, }; +use reth_rpc_types::TransactionRequest; use revm_primitives::{AccessListItem, SpecId, TransactTo, TxEnv}; use crate::era::types::TransactionsWithSender; @@ -129,6 +130,42 @@ impl TxEnvModifier for BlobTransaction { } } +impl TxEnvModifier for TransactionRequest { + fn modify(&self, _block_number: u64, tx_env: &mut TxEnv) { + if let Some(from) = self.from { + tx_env.caller = from; + } + if let Some(to) = self.to { + tx_env.transact_to = to; + } + if let Some(gas_price) = self.gas_price { + tx_env.gas_price = U256::from(gas_price); + } + if let Some(max_fee_per_gas) = self.max_fee_per_gas { + tx_env.gas_price = U256::from(max_fee_per_gas); + } + tx_env.gas_priority_fee = self.max_priority_fee_per_gas.map(U256::from); + tx_env.max_fee_per_blob_gas = self.max_fee_per_blob_gas.map(U256::from); + if let Some(gas) = self.gas { + tx_env.gas_limit = gas as u64; + } + if let Some(value) = self.value { + tx_env.value = value; + } + if let Some(data) = self.input.input() { + tx_env.data.clone_from(data); + } + tx_env.nonce = self.nonce; + tx_env.chain_id = self.chain_id; + if let Some(access_list) = &self.access_list { + tx_env.access_list.clone_from(access_list); + } + if let Some(blob_versioned_hashes) = &self.blob_versioned_hashes { + tx_env.blob_hashes.clone_from(blob_versioned_hashes); + } + } +} + #[cfg(test)] mod tests { use super::*;