openzeppelin_relayer/services/gas/
network_extra_fee.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use async_trait::async_trait;

use crate::{
    models::{EvmNetwork, EvmTransactionData, TransactionError, U256},
    services::EvmProviderTrait,
};

use super::optimism_extra_fee::OptimismExtraFeeService;

#[cfg(test)]
use mockall::automock;

#[async_trait]
#[cfg_attr(test, automock)]
pub trait NetworkExtraFeeCalculatorServiceTrait {
    /// Get the extra fee for a transaction
    ///
    /// # Arguments
    ///
    /// * `tx_data` - The transaction data to get the extra fee for
    ///
    /// # Returns
    ///
    async fn get_extra_fee(&self, tx_data: &EvmTransactionData) -> Result<U256, TransactionError>;
}

/// Enum of network-specific extra fee calculators
pub enum NetworkExtraFeeCalculator<P: EvmProviderTrait> {
    /// No extra fee calculator
    None,
    /// Optimism extra fee calculator
    Optimism(OptimismExtraFeeService<P>),
    /// Test mock implementation (available only in test builds)
    #[cfg(test)]
    Mock(MockNetworkExtraFeeCalculatorServiceTrait),
}

#[async_trait]
impl<P: EvmProviderTrait + Send + Sync> NetworkExtraFeeCalculatorServiceTrait
    for NetworkExtraFeeCalculator<P>
{
    async fn get_extra_fee(&self, tx_data: &EvmTransactionData) -> Result<U256, TransactionError> {
        match self {
            Self::None => Ok(U256::ZERO),
            Self::Optimism(service) => service.get_extra_fee(tx_data).await,
            #[cfg(test)]
            Self::Mock(mock) => mock.get_extra_fee(tx_data).await,
        }
    }
}

/// Get the network extra fee calculator service
///
/// # Arguments
///
/// * `network` - The network to get the extra fee calculator service for
/// * `provider` - The provider to get the extra fee calculator service for
///
pub fn get_network_extra_fee_calculator_service<P>(
    network: EvmNetwork,
    provider: P,
) -> NetworkExtraFeeCalculator<P>
where
    P: EvmProviderTrait + 'static,
{
    if network.is_optimism() {
        NetworkExtraFeeCalculator::Optimism(OptimismExtraFeeService::new(provider))
    } else {
        NetworkExtraFeeCalculator::None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::{EvmNamedNetwork, EvmNetwork};
    use crate::services::MockEvmProviderTrait;
    use alloy::primitives::Bytes;

    #[test]
    fn test_get_network_extra_fee_calculator_service_for_optimism() {
        let provider = MockEvmProviderTrait::new();
        let network = EvmNetwork::from_named(EvmNamedNetwork::Optimism);
        let service = get_network_extra_fee_calculator_service(network, provider);

        assert!(
            matches!(service, NetworkExtraFeeCalculator::Optimism(_)),
            "Should return an Optimism service for Optimism network"
        );
    }

    #[test]
    fn test_get_network_extra_fee_calculator_service_for_non_optimism() {
        let networks = [
            EvmNetwork::from_named(EvmNamedNetwork::Mainnet),
            EvmNetwork::from_named(EvmNamedNetwork::Arbitrum),
            EvmNetwork::from_named(EvmNamedNetwork::Polygon),
        ];

        for network in networks {
            let provider = MockEvmProviderTrait::new();
            let service = get_network_extra_fee_calculator_service(network, provider);

            assert!(
                matches!(service, NetworkExtraFeeCalculator::None),
                "Should return None service for non-Optimism network"
            );
        }
    }

    #[tokio::test]
    async fn test_integration_with_optimism_extra_fee_service() {
        let mut mock_provider = MockEvmProviderTrait::new();

        mock_provider
            .expect_call_contract()
            .times(6) // All 6 contract calls in get_modifiers
            .returning(|_| {
                let value_bytes = U256::from(1u64).to_be_bytes::<32>();
                Box::pin(async move { Ok(Bytes::from(value_bytes.to_vec())) })
            });

        let network = EvmNetwork::from_named(EvmNamedNetwork::Optimism);
        let service = get_network_extra_fee_calculator_service(network, mock_provider);

        let tx_data = EvmTransactionData {
            from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
            to: Some("0xa24Cea55A6171FbA0935c9e171c4Efe5Ba28DF91".to_string()),
            gas_price: Some(20000000000),
            value: U256::from(1000000000),
            data: Some("0x0123".to_string()),
            nonce: Some(1),
            chain_id: 10,
            gas_limit: 21000,
            hash: None,
            signature: None,
            speed: None,
            max_fee_per_gas: None,
            max_priority_fee_per_gas: None,
            raw: None,
        };

        let extra_fee_result = service.get_extra_fee(&tx_data).await;

        assert!(
            extra_fee_result.is_ok(),
            "Should calculate extra fee without errors"
        );

        let extra_fee = extra_fee_result.unwrap();
        assert!(
            extra_fee > U256::ZERO,
            "Extra fee should be greater than zero"
        );
    }
}