openzeppelin_relayer/services/gas/
network_extra_fee.rs

1use async_trait::async_trait;
2
3use crate::{
4    models::{EvmNetwork, EvmTransactionData, TransactionError, U256},
5    services::EvmProviderTrait,
6};
7
8use super::optimism_extra_fee::OptimismExtraFeeService;
9
10#[cfg(test)]
11use mockall::automock;
12
13#[async_trait]
14#[cfg_attr(test, automock)]
15pub trait NetworkExtraFeeCalculatorServiceTrait {
16    /// Get the extra fee for a transaction
17    ///
18    /// # Arguments
19    ///
20    /// * `tx_data` - The transaction data to get the extra fee for
21    ///
22    /// # Returns
23    ///
24    async fn get_extra_fee(&self, tx_data: &EvmTransactionData) -> Result<U256, TransactionError>;
25}
26
27/// Enum of network-specific extra fee calculators
28pub enum NetworkExtraFeeCalculator<P: EvmProviderTrait> {
29    /// No extra fee calculator
30    None,
31    /// Optimism extra fee calculator
32    Optimism(OptimismExtraFeeService<P>),
33    /// Test mock implementation (available only in test builds)
34    #[cfg(test)]
35    Mock(MockNetworkExtraFeeCalculatorServiceTrait),
36}
37
38#[async_trait]
39impl<P: EvmProviderTrait + Send + Sync> NetworkExtraFeeCalculatorServiceTrait
40    for NetworkExtraFeeCalculator<P>
41{
42    async fn get_extra_fee(&self, tx_data: &EvmTransactionData) -> Result<U256, TransactionError> {
43        match self {
44            Self::None => Ok(U256::ZERO),
45            Self::Optimism(service) => service.get_extra_fee(tx_data).await,
46            #[cfg(test)]
47            Self::Mock(mock) => mock.get_extra_fee(tx_data).await,
48        }
49    }
50}
51
52/// Get the network extra fee calculator service
53///
54/// # Arguments
55///
56/// * `network` - The network to get the extra fee calculator service for
57/// * `provider` - The provider to get the extra fee calculator service for
58///
59pub fn get_network_extra_fee_calculator_service<P>(
60    network: EvmNetwork,
61    provider: P,
62) -> NetworkExtraFeeCalculator<P>
63where
64    P: EvmProviderTrait + 'static,
65{
66    if network.is_optimism() {
67        NetworkExtraFeeCalculator::Optimism(OptimismExtraFeeService::new(provider))
68    } else {
69        NetworkExtraFeeCalculator::None
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::models::EvmNetwork;
77    use crate::services::MockEvmProviderTrait;
78    use alloy::primitives::Bytes;
79
80    fn create_test_evm_network(name: &str, optimism: bool) -> EvmNetwork {
81        EvmNetwork {
82            network: name.to_string(),
83            rpc_urls: vec!["https://optimism-rpc.com".to_string()],
84            explorer_urls: None,
85            average_blocktime_ms: 12000,
86            is_testnet: false,
87            tags: vec![if optimism { "optimism" } else { name }.to_string()],
88            chain_id: if optimism { 10 } else { 42161 },
89            required_confirmations: 1,
90            features: vec!["eip1559".to_string()],
91            symbol: "ETH".to_string(),
92        }
93    }
94
95    #[test]
96    fn test_get_network_extra_fee_calculator_service_for_optimism() {
97        let provider = MockEvmProviderTrait::new();
98        let network = create_test_evm_network("optimism", true);
99        let service = get_network_extra_fee_calculator_service(network, provider);
100
101        assert!(
102            matches!(service, NetworkExtraFeeCalculator::Optimism(_)),
103            "Should return an Optimism service for Optimism network"
104        );
105    }
106
107    #[test]
108    fn test_get_network_extra_fee_calculator_service_for_non_optimism() {
109        let networks = [
110            create_test_evm_network("mainnet", false),
111            create_test_evm_network("arbitrum", false),
112            create_test_evm_network("polygon", false),
113        ];
114
115        for network in networks {
116            let provider = MockEvmProviderTrait::new();
117            let service = get_network_extra_fee_calculator_service(network, provider);
118
119            assert!(
120                matches!(service, NetworkExtraFeeCalculator::None),
121                "Should return None service for non-Optimism network"
122            );
123        }
124    }
125
126    #[tokio::test]
127    async fn test_integration_with_optimism_extra_fee_service() {
128        let mut mock_provider = MockEvmProviderTrait::new();
129
130        mock_provider
131            .expect_call_contract()
132            .times(6) // All 6 contract calls in get_modifiers
133            .returning(|_| {
134                let value_bytes = U256::from(1u64).to_be_bytes::<32>();
135                Box::pin(async move { Ok(Bytes::from(value_bytes.to_vec())) })
136            });
137
138        let network = create_test_evm_network("optimism", true);
139        let service = get_network_extra_fee_calculator_service(network, mock_provider);
140
141        let tx_data = EvmTransactionData {
142            from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
143            to: Some("0xa24Cea55A6171FbA0935c9e171c4Efe5Ba28DF91".to_string()),
144            gas_price: Some(20000000000),
145            value: U256::from(1000000000),
146            data: Some("0x0123".to_string()),
147            nonce: Some(1),
148            chain_id: 10,
149            gas_limit: Some(21000),
150            hash: None,
151            signature: None,
152            speed: None,
153            max_fee_per_gas: None,
154            max_priority_fee_per_gas: None,
155            raw: None,
156        };
157
158        let extra_fee_result = service.get_extra_fee(&tx_data).await;
159
160        assert!(
161            extra_fee_result.is_ok(),
162            "Should calculate extra fee without errors"
163        );
164
165        let extra_fee = extra_fee_result.unwrap();
166        assert!(
167            extra_fee > U256::ZERO,
168            "Extra fee should be greater than zero"
169        );
170    }
171}