reth_rpc/eth/helpers/
types.rs

1//! L1 `eth` API types.
2
3use alloy_consensus::{SignableTransaction, 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        let signature = Signature::new(Default::default(), Default::default(), false);
75        Ok(tx.into_signed(signature).into())
76    }
77
78    fn otterscan_api_truncate_input(tx: &mut Self::Transaction) {
79        let input = tx.inner.inner_mut().input_mut();
80        *input = input.slice(..4);
81    }
82}
83
84//tests for simulate
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use alloy_consensus::TxType;
89    use reth_rpc_eth_types::simulate::resolve_transaction;
90    use revm::database::CacheDB;
91
92    #[test]
93    fn test_resolve_transaction_empty_request() {
94        let builder = EthTxBuilder::default();
95        let mut db = CacheDB::<reth_revm::db::EmptyDBTyped<reth_errors::ProviderError>>::default();
96        let tx = TransactionRequest::default();
97        let result = resolve_transaction(tx, 21000, 0, 1, &mut db, &builder).unwrap();
98
99        // For an empty request, we should get a valid transaction with defaults
100        let tx = result.into_inner();
101        assert_eq!(tx.max_fee_per_gas(), 0);
102        assert_eq!(tx.max_priority_fee_per_gas(), Some(0));
103        assert_eq!(tx.gas_price(), None);
104    }
105
106    #[test]
107    fn test_resolve_transaction_legacy() {
108        let mut db = CacheDB::<reth_revm::db::EmptyDBTyped<reth_errors::ProviderError>>::default();
109        let builder = EthTxBuilder::default();
110
111        let tx = TransactionRequest { gas_price: Some(100), ..Default::default() };
112
113        let tx = resolve_transaction(tx, 21000, 0, 1, &mut db, &builder).unwrap();
114
115        assert_eq!(tx.tx_type(), TxType::Legacy);
116
117        let tx = tx.into_inner();
118        assert_eq!(tx.gas_price(), Some(100));
119        assert_eq!(tx.max_priority_fee_per_gas(), None);
120    }
121
122    #[test]
123    fn test_resolve_transaction_partial_eip1559() {
124        let mut db = CacheDB::<reth_revm::db::EmptyDBTyped<reth_errors::ProviderError>>::default();
125        let builder = EthTxBuilder::default();
126
127        let tx = TransactionRequest {
128            max_fee_per_gas: Some(200),
129            max_priority_fee_per_gas: Some(10),
130            ..Default::default()
131        };
132
133        let result = resolve_transaction(tx, 21000, 0, 1, &mut db, &builder).unwrap();
134
135        assert_eq!(result.tx_type(), TxType::Eip1559);
136        let tx = result.into_inner();
137        assert_eq!(tx.max_fee_per_gas(), 200);
138        assert_eq!(tx.max_priority_fee_per_gas(), Some(10));
139        assert_eq!(tx.gas_price(), None);
140    }
141}