reth_rpc/eth/helpers/
types.rs

1//! L1 `eth` API types.
2
3use alloy_consensus::{Transaction as _, TxEnvelope};
4use alloy_network::{Ethereum, Network};
5use alloy_primitives::Signature;
6use alloy_rpc_types::TransactionRequest;
7use alloy_rpc_types_eth::{Transaction, TransactionInfo};
8use reth_ethereum_primitives::TransactionSigned;
9use reth_primitives_traits::Recovered;
10use reth_rpc_eth_api::EthApiTypes;
11use reth_rpc_eth_types::EthApiError;
12use reth_rpc_types_compat::TransactionCompat;
13
14/// A standalone [`EthApiTypes`] implementation for Ethereum.
15#[derive(Debug, Clone, Copy, Default)]
16pub struct EthereumEthApiTypes(EthTxBuilder);
17
18impl EthApiTypes for EthereumEthApiTypes {
19    type Error = EthApiError;
20    type NetworkTypes = Ethereum;
21    type TransactionCompat = EthTxBuilder;
22
23    fn tx_resp_builder(&self) -> &Self::TransactionCompat {
24        &self.0
25    }
26}
27
28/// Builds RPC transaction response for l1.
29#[derive(Debug, Clone, Copy, Default)]
30#[non_exhaustive]
31pub struct EthTxBuilder;
32
33impl TransactionCompat<TransactionSigned> for EthTxBuilder
34where
35    Self: Send + Sync,
36{
37    type Transaction = <Ethereum as Network>::TransactionResponse;
38
39    type Error = EthApiError;
40
41    fn fill(
42        &self,
43        tx: Recovered<TransactionSigned>,
44        tx_info: TransactionInfo,
45    ) -> Result<Self::Transaction, Self::Error> {
46        let tx = tx.convert::<TxEnvelope>();
47
48        let TransactionInfo {
49            block_hash, block_number, index: transaction_index, base_fee, ..
50        } = tx_info;
51
52        let effective_gas_price = base_fee
53            .map(|base_fee| {
54                tx.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128
55            })
56            .unwrap_or_else(|| tx.max_fee_per_gas());
57
58        Ok(Transaction {
59            inner: tx,
60            block_hash,
61            block_number,
62            transaction_index,
63            effective_gas_price: Some(effective_gas_price),
64        })
65    }
66
67    fn build_simulate_v1_transaction(
68        &self,
69        request: TransactionRequest,
70    ) -> Result<TransactionSigned, Self::Error> {
71        let Ok(tx) = request.build_typed_tx() else {
72            return Err(EthApiError::TransactionConversionError)
73        };
74
75        // Create an empty signature for the transaction.
76        let signature = Signature::new(Default::default(), Default::default(), false);
77        Ok(TransactionSigned::new_unhashed(tx.into(), signature))
78    }
79
80    fn otterscan_api_truncate_input(tx: &mut Self::Transaction) {
81        let input = tx.inner.inner_mut().input_mut();
82        *input = input.slice(..4);
83    }
84}
85
86//tests for simulate
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use alloy_consensus::TxType;
91    use reth_rpc_eth_types::simulate::resolve_transaction;
92    use revm::database::CacheDB;
93
94    #[test]
95    fn test_resolve_transaction_empty_request() {
96        let builder = EthTxBuilder::default();
97        let mut db = CacheDB::<reth_revm::db::EmptyDBTyped<reth_errors::ProviderError>>::default();
98        let tx = TransactionRequest::default();
99        let result = resolve_transaction(tx, 21000, 0, 1, &mut db, &builder).unwrap();
100
101        // For an empty request, we should get a valid transaction with defaults
102        let tx = result.into_inner();
103        assert_eq!(tx.max_fee_per_gas(), 0);
104        assert_eq!(tx.max_priority_fee_per_gas(), Some(0));
105        assert_eq!(tx.gas_price(), None);
106    }
107
108    #[test]
109    fn test_resolve_transaction_legacy() {
110        let mut db = CacheDB::<reth_revm::db::EmptyDBTyped<reth_errors::ProviderError>>::default();
111        let builder = EthTxBuilder::default();
112
113        let tx = TransactionRequest { gas_price: Some(100), ..Default::default() };
114
115        let tx = resolve_transaction(tx, 21000, 0, 1, &mut db, &builder).unwrap();
116
117        assert_eq!(tx.tx_type(), TxType::Legacy);
118
119        let tx = tx.into_inner();
120        assert_eq!(tx.gas_price(), Some(100));
121        assert_eq!(tx.max_priority_fee_per_gas(), None);
122    }
123
124    #[test]
125    fn test_resolve_transaction_partial_eip1559() {
126        let mut db = CacheDB::<reth_revm::db::EmptyDBTyped<reth_errors::ProviderError>>::default();
127        let builder = EthTxBuilder::default();
128
129        let tx = TransactionRequest {
130            max_fee_per_gas: Some(200),
131            max_priority_fee_per_gas: Some(10),
132            ..Default::default()
133        };
134
135        let result = resolve_transaction(tx, 21000, 0, 1, &mut db, &builder).unwrap();
136
137        assert_eq!(result.tx_type(), TxType::Eip1559);
138        let tx = result.into_inner();
139        assert_eq!(tx.max_fee_per_gas(), 200);
140        assert_eq!(tx.max_priority_fee_per_gas(), Some(10));
141        assert_eq!(tx.gas_price(), None);
142    }
143}