openzeppelin_relayer/domain/relayer/evm/
validations.rs

1use thiserror::Error;
2
3use crate::{
4    constants::DEFAULT_EVM_MIN_BALANCE,
5    models::{types::U256, RelayerEvmPolicy},
6    services::EvmProviderTrait,
7};
8
9#[derive(Debug, Error)]
10pub enum EvmTransactionValidationError {
11    #[error("Provider error: {0}")]
12    ProviderError(String),
13    #[error("Validation error: {0}")]
14    ValidationError(String),
15    #[error("Insufficient balance: {0}")]
16    InsufficientBalance(String),
17}
18
19pub struct EvmTransactionValidator {}
20
21impl EvmTransactionValidator {
22    pub async fn init_balance_validation(
23        relayer_address: &str,
24        policy: &RelayerEvmPolicy,
25        provider: &impl EvmProviderTrait,
26    ) -> Result<(), EvmTransactionValidationError> {
27        let balance = provider
28            .get_balance(relayer_address)
29            .await
30            .map_err(|e| EvmTransactionValidationError::ProviderError(e.to_string()))?;
31
32        let min_balance = U256::from(policy.min_balance.unwrap_or(DEFAULT_EVM_MIN_BALANCE));
33
34        if balance < min_balance {
35            return Err(EvmTransactionValidationError::InsufficientBalance(format!(
36                "Relayer balance ({}) is below minimum required balance ({})",
37                balance, min_balance
38            )));
39        }
40
41        Ok(())
42    }
43
44    pub async fn validate_sufficient_relayer_balance(
45        balance_to_use: U256,
46        relayer_address: &str,
47        policy: &RelayerEvmPolicy,
48        provider: &impl EvmProviderTrait,
49    ) -> Result<(), EvmTransactionValidationError> {
50        let balance = provider
51            .get_balance(relayer_address)
52            .await
53            .map_err(|e| EvmTransactionValidationError::ProviderError(e.to_string()))?;
54
55        let min_balance = U256::from(policy.min_balance.unwrap_or(DEFAULT_EVM_MIN_BALANCE));
56
57        let remaining_balance = balance.saturating_sub(balance_to_use);
58
59        // Check if balance is insufficient to cover transaction cost
60        if balance < balance_to_use {
61            return Err(EvmTransactionValidationError::InsufficientBalance(format!(
62                "Relayer balance {balance} is insufficient to cover {balance_to_use}"
63            )));
64        }
65
66        // Check if remaining balance would fall below minimum requirement
67        if !min_balance.is_zero() && remaining_balance < min_balance {
68            return Err(EvmTransactionValidationError::InsufficientBalance(
69                format!("Relayer balance {balance} is insufficient to cover {balance_to_use}, with an enforced minimum balance of {}", policy.min_balance.unwrap_or(DEFAULT_EVM_MIN_BALANCE))
70            ));
71        }
72
73        Ok(())
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use std::future::ready;
80
81    use super::*;
82    use crate::services::provider::evm::MockEvmProviderTrait;
83    use crate::services::ProviderError;
84    use mockall::predicate::*;
85
86    fn create_test_policy(min_balance: u128) -> RelayerEvmPolicy {
87        RelayerEvmPolicy {
88            min_balance: Some(min_balance),
89            gas_limit_estimation: Some(true),
90            gas_price_cap: None,
91            whitelist_receivers: None,
92            eip1559_pricing: None,
93            private_transactions: Some(false),
94        }
95    }
96
97    #[tokio::test]
98    async fn test_validate_sufficient_balance_routine_check_success() {
99        let mut mock_provider = MockEvmProviderTrait::new();
100        mock_provider
101            .expect_get_balance()
102            .with(eq("0xSender"))
103            .returning(|_| Box::pin(ready(Ok(U256::from(200000000000000000u64))))); // 0.2 ETH
104
105        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
106            U256::ZERO,
107            "0xSender",
108            &create_test_policy(100000000000000000u128), // 0.1 ETH min balance
109            &mock_provider,
110        )
111        .await;
112
113        assert!(result.is_ok());
114    }
115
116    #[tokio::test]
117    async fn test_validate_sufficient_balance_routine_check_failure() {
118        let mut mock_provider = MockEvmProviderTrait::new();
119        mock_provider
120            .expect_get_balance()
121            .with(eq("0xSender"))
122            .returning(|_| Box::pin(ready(Ok(U256::from(50000000000000000u64))))); // 0.05 ETH
123
124        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
125            U256::ZERO,
126            "0xSender",
127            &create_test_policy(100000000000000000u128), // 0.1 ETH min balance
128            &mock_provider,
129        )
130        .await;
131
132        assert!(matches!(
133            result,
134            Err(EvmTransactionValidationError::InsufficientBalance(_))
135        ));
136    }
137
138    #[tokio::test]
139    async fn test_validate_sufficient_balance_with_transaction_success() {
140        let mut mock_provider = MockEvmProviderTrait::new();
141        mock_provider
142            .expect_get_balance()
143            .with(eq("0xSender"))
144            .returning(|_| Box::pin(ready(Ok(U256::from(300000000000000000u64))))); // 0.3 ETH
145
146        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
147            U256::from(100000000000000000u64), // 0.1 ETH to use
148            "0xSender",
149            &create_test_policy(100000000000000000u128), // 0.1 ETH min balance
150            &mock_provider,
151        )
152        .await;
153
154        assert!(result.is_ok());
155    }
156
157    #[tokio::test]
158    async fn test_validate_sufficient_balance_with_transaction_failure() {
159        let mut mock_provider = MockEvmProviderTrait::new();
160        mock_provider
161            .expect_get_balance()
162            .with(eq("0xSender"))
163            .returning(|_| Box::pin(ready(Ok(U256::from(150000000000000000u64))))); // 0.15 ETH
164
165        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
166            U256::from(100000000000000000u64), // 0.1 ETH to use
167            "0xSender",
168            &create_test_policy(100000000000000000u128), // 0.1 ETH min balance
169            &mock_provider,
170        )
171        .await;
172
173        assert!(matches!(
174            result,
175            Err(EvmTransactionValidationError::InsufficientBalance(_))
176        ));
177    }
178
179    #[tokio::test]
180    async fn test_validate_provider_error() {
181        let mut mock_provider = MockEvmProviderTrait::new();
182        mock_provider
183            .expect_get_balance()
184            .with(eq("0xSender"))
185            .returning(|_| {
186                Box::pin(ready(Err(ProviderError::Other(
187                    "Provider error".to_string(),
188                ))))
189            });
190
191        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
192            U256::ZERO,
193            "0xSender",
194            &create_test_policy(100000000000000000u128),
195            &mock_provider,
196        )
197        .await;
198
199        assert!(matches!(
200            result,
201            Err(EvmTransactionValidationError::ProviderError(_))
202        ));
203    }
204
205    #[tokio::test]
206    async fn test_validate_no_min_balance_success() {
207        let mut mock_provider = MockEvmProviderTrait::new();
208        mock_provider
209            .expect_get_balance()
210            .with(eq("0xSender"))
211            .returning(|_| Box::pin(ready(Ok(U256::from(100000000000000000u64))))); // 0.1 ETH
212
213        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
214            U256::from(50000000000000000u64), // 0.05 ETH to use
215            "0xSender",
216            &create_test_policy(0), // No min balance
217            &mock_provider,
218        )
219        .await;
220
221        assert!(result.is_ok());
222    }
223
224    #[tokio::test]
225    async fn test_validate_no_min_balance_failure() {
226        let mut mock_provider = MockEvmProviderTrait::new();
227        mock_provider
228            .expect_get_balance()
229            .with(eq("0xSender"))
230            .returning(|_| Box::pin(ready(Ok(U256::from(100000000000000000u64))))); // 0.1 ETH
231
232        let result = EvmTransactionValidator::validate_sufficient_relayer_balance(
233            U256::from(150000000000000000u64), // 0.15 ETH to use
234            "0xSender",
235            &create_test_policy(0), // No min balance
236            &mock_provider,
237        )
238        .await;
239
240        assert!(matches!(
241            result,
242            Err(EvmTransactionValidationError::InsufficientBalance(_))
243        ));
244    }
245
246    #[tokio::test]
247    async fn test_init_balance_validation_success() {
248        let mut mock_provider = MockEvmProviderTrait::new();
249        mock_provider
250            .expect_get_balance()
251            .with(eq("0xSender"))
252            .returning(|_| Box::pin(ready(Ok(U256::from(200000000000000000u64)))));
253
254        let result = EvmTransactionValidator::init_balance_validation(
255            "0xSender",
256            &create_test_policy(100000000000000000u128),
257            &mock_provider,
258        )
259        .await;
260
261        assert!(result.is_ok());
262    }
263
264    #[tokio::test]
265    async fn test_init_balance_validation_failure() {
266        let mut mock_provider = MockEvmProviderTrait::new();
267        mock_provider
268            .expect_get_balance()
269            .with(eq("0xSender"))
270            .returning(|_| Box::pin(ready(Ok(U256::from(50000000000000000u64)))));
271
272        let result = EvmTransactionValidator::init_balance_validation(
273            "0xSender",
274            &create_test_policy(100000000000000000u128),
275            &mock_provider,
276        )
277        .await;
278
279        assert!(matches!(
280            result,
281            Err(EvmTransactionValidationError::InsufficientBalance(_))
282        ));
283    }
284
285    #[tokio::test]
286    async fn test_init_balance_validation_provider_error() {
287        let mut mock_provider = MockEvmProviderTrait::new();
288        mock_provider
289            .expect_get_balance()
290            .with(eq("0xSender"))
291            .returning(|_| {
292                Box::pin(ready(Err(ProviderError::Other(
293                    "Provider error".to_string(),
294                ))))
295            });
296
297        let result = EvmTransactionValidator::init_balance_validation(
298            "0xSender",
299            &create_test_policy(100000000000000000u128),
300            &mock_provider,
301        )
302        .await;
303
304        assert!(matches!(
305            result,
306            Err(EvmTransactionValidationError::ProviderError(_))
307        ));
308    }
309
310    #[tokio::test]
311    async fn test_init_balance_validation_zero_min_balance() {
312        let mut mock_provider = MockEvmProviderTrait::new();
313        mock_provider
314            .expect_get_balance()
315            .with(eq("0xSender"))
316            .returning(|_| Box::pin(ready(Ok(U256::from(0u64)))));
317
318        let result = EvmTransactionValidator::init_balance_validation(
319            "0xSender",
320            &create_test_policy(0), // No min balance
321            &mock_provider,
322        )
323        .await;
324
325        assert!(result.is_ok());
326    }
327}