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