openzeppelin_relayer/domain/relayer/evm/
validations.rs

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