1use crate::{
2 constants::ZERO_ADDRESS,
3 models::{ApiError, RelayerNetworkPolicy, RelayerRepoModel, U256},
4 utils::calculate_intrinsic_gas,
5};
6use serde::{Deserialize, Serialize};
7use utoipa::{schema, ToSchema};
8
9#[derive(Deserialize, Serialize, Default, ToSchema)]
10pub struct EvmTransactionRequest {
11 #[schema(nullable = false)]
12 pub to: Option<String>,
13 #[schema(value_type = u128, format = "u128")]
14 pub value: U256,
15 #[schema(nullable = false)]
16 pub data: Option<String>,
17 pub gas_limit: Option<u64>,
18 #[schema(nullable = false)]
19 pub gas_price: Option<u128>,
20 #[schema(nullable = false)]
21 pub speed: Option<Speed>,
22 #[schema(nullable = false)]
23 pub max_fee_per_gas: Option<u128>,
24 #[schema(nullable = false)]
25 pub max_priority_fee_per_gas: Option<u128>,
26 #[schema(nullable = false)]
27 pub valid_until: Option<String>,
28}
29
30#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
31#[serde(rename_all = "lowercase")]
32pub enum Speed {
33 Fastest,
34 Fast,
35 Average,
36 #[serde(rename = "safeLow")]
37 SafeLow,
38}
39impl EvmTransactionRequest {
40 pub fn validate(&self, relayer: &RelayerRepoModel) -> Result<(), ApiError> {
41 validate_target_address(self, relayer)?;
42 validate_evm_transaction_request(self, relayer)?;
43 validate_price_params(self, relayer)?;
44 Ok(())
45 }
46}
47
48pub fn validate_evm_transaction_request(
49 request: &EvmTransactionRequest,
50 relayer: &RelayerRepoModel,
51) -> Result<(), ApiError> {
52 if request.to.is_none() && request.data.is_none() {
53 return Err(ApiError::BadRequest(
54 "Both txs `to` and `data` fields are missing. At least one of them has to be set."
55 .to_string(),
56 ));
57 }
58
59 if let RelayerNetworkPolicy::Evm(evm_policy) = &relayer.policies {
61 if evm_policy.gas_limit_estimation == Some(false) && request.gas_limit.is_none() {
63 return Err(ApiError::BadRequest(
64 "gas_limit is required when gas_limit_estimation policy is disabled".to_string(),
65 ));
66 }
67 }
68
69 if let Some(gas_limit) = request.gas_limit {
71 let intrinsic_gas = calculate_intrinsic_gas(request);
72 if gas_limit < intrinsic_gas {
73 return Err(ApiError::BadRequest(format!(
74 "gas_limit is too low, intrinsic gas is {} and gas_limit is {}",
75 intrinsic_gas, gas_limit
76 )));
77 }
78 }
79
80 if let Some(valid_until) = &request.valid_until {
81 match chrono::DateTime::parse_from_rfc3339(valid_until) {
82 Ok(valid_until_dt) => {
83 let now = chrono::Utc::now();
84 if valid_until_dt < now {
85 return Err(ApiError::BadRequest(
86 "The validUntil time cannot be in the past".to_string(),
87 ));
88 }
89 }
90 Err(_) => {
91 return Err(ApiError::BadRequest(
92 "Invalid validUntil datetime format".to_string(),
93 ));
94 }
95 }
96 }
97
98 Ok(())
99}
100
101pub fn validate_target_address(
102 request: &EvmTransactionRequest,
103 relayer: &RelayerRepoModel,
104) -> Result<(), ApiError> {
105 if let RelayerNetworkPolicy::Evm(evm_policy) = &relayer.policies {
106 if let Some(whitelist) = &evm_policy.whitelist_receivers {
107 let target_address = request.to.clone().unwrap_or_default().to_lowercase();
108 let mut allowed_addresses: Vec<String> =
109 whitelist.iter().map(|addr| addr.to_lowercase()).collect();
110 allowed_addresses.push(ZERO_ADDRESS.to_string());
111 allowed_addresses.push(relayer.address.to_lowercase());
112
113 if !allowed_addresses.contains(&target_address) {
114 return Err(ApiError::BadRequest(
115 "Transaction target address is not whitelisted".to_string(),
116 ));
117 }
118 }
119 }
120 Ok(())
121}
122
123pub fn validate_price_params(
124 request: &EvmTransactionRequest,
125 relayer: &RelayerRepoModel,
126) -> Result<(), ApiError> {
127 let is_eip1559 =
128 request.max_fee_per_gas.is_some() || request.max_priority_fee_per_gas.is_some();
129 let is_legacy = request.gas_price.is_some();
130 let is_speed = request.speed.is_some();
131
132 let transaction_types = [is_eip1559, is_legacy, is_speed]
134 .iter()
135 .filter(|&&x| x)
136 .count();
137
138 if transaction_types == 0 {
140 return Err(ApiError::BadRequest(
141 "Transaction must specify either gasPrice, speed, or EIP1559 parameters".to_string(),
142 ));
143 }
144
145 if transaction_types > 1 {
146 return Err(ApiError::BadRequest(
147 "Cannot mix different transaction types. Use either gasPrice, speed, or EIP1559 \
148 parameters"
149 .to_string(),
150 ));
151 }
152
153 if is_eip1559 {
155 match (request.max_fee_per_gas, request.max_priority_fee_per_gas) {
157 (Some(_), None) | (None, Some(_)) => {
158 return Err(ApiError::BadRequest(
159 "EIP1559 transactions require both maxFeePerGas and maxPriorityFeePerGas"
160 .to_string(),
161 ));
162 }
163 (Some(max_fee), Some(max_priority_fee)) => {
164 if max_fee < max_priority_fee {
165 return Err(ApiError::BadRequest(
166 "maxFeePerGas must be greater than or equal to maxPriorityFeePerGas"
167 .to_string(),
168 ));
169 }
170 }
171 _ => unreachable!(),
172 }
173 }
174
175 if is_legacy {
176 if let RelayerNetworkPolicy::Evm(evm_policy) = &relayer.policies {
177 if let Some(gas_price_cap) = evm_policy.gas_price_cap {
178 if request.gas_price.unwrap_or(0) > gas_price_cap {
179 return Err(ApiError::BadRequest("Gas price is too high".to_string()));
180 }
181 }
182 }
183 }
184
185 Ok(())
186}
187
188#[cfg(test)]
189mod tests {
190 use crate::models::{NetworkType, RelayerEvmPolicy, RelayerNetworkPolicy, RpcConfig};
191
192 use super::*;
193 use chrono::{Duration, Utc};
194
195 fn create_basic_request() -> EvmTransactionRequest {
196 EvmTransactionRequest {
197 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
198 value: U256::from(0),
199 data: Some("0x".to_string()),
200 gas_limit: Some(21000),
201 gas_price: Some(0),
202 speed: None,
203 max_fee_per_gas: None,
204 max_priority_fee_per_gas: None,
205 valid_until: None,
206 }
207 }
208
209 fn create_test_relayer(paused: bool, system_disabled: bool) -> RelayerRepoModel {
210 RelayerRepoModel {
211 id: "test_relayer".to_string(),
212 name: "Test Relayer".to_string(),
213 paused,
214 system_disabled,
215 network: "test_network".to_string(),
216 network_type: NetworkType::Evm,
217 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
218 signer_id: "test_signer".to_string(),
219 address: "0x".to_string(),
220 notification_id: None,
221 custom_rpc_urls: Some(vec![RpcConfig::new("https://test-rpc-url".to_string())]),
222 ..Default::default()
223 }
224 }
225
226 #[test]
227 fn test_validate_evm_transaction_request_valid() {
228 let request = create_basic_request();
229 assert!(
230 validate_evm_transaction_request(&request, &create_test_relayer(false, false)).is_ok()
231 );
232 }
233
234 #[test]
235 fn test_validate_missing_to_and_data() {
236 let mut request = create_basic_request();
237 request.to = None;
238 request.data = None;
239
240 let result = validate_evm_transaction_request(&request, &create_test_relayer(false, false));
241 assert!(result.is_err());
242 assert!(matches!(result, Err(ApiError::BadRequest(_))));
243 }
244
245 #[test]
246 fn test_validate_valid_until_past() {
247 let mut request = create_basic_request();
248 let past_time = Utc::now() - Duration::hours(1);
249 request.valid_until = Some(past_time.to_rfc3339());
250
251 let result = validate_evm_transaction_request(&request, &create_test_relayer(false, false));
252 assert!(result.is_err());
253 assert!(matches!(result, Err(ApiError::BadRequest(_))));
254 }
255
256 #[test]
257 fn test_validate_valid_until_future() {
258 let mut request = create_basic_request();
259 let future_time = Utc::now() + Duration::hours(1);
260 request.valid_until = Some(future_time.to_rfc3339());
261
262 assert!(
263 validate_evm_transaction_request(&request, &create_test_relayer(false, false)).is_ok()
264 );
265 }
266
267 #[test]
268 fn test_validate_target_address_whitelisted() {
269 let request = create_basic_request();
270 let relayer = create_test_relayer(false, false);
271
272 assert!(validate_target_address(&request, &relayer).is_ok());
273 }
274
275 #[test]
276 fn test_validate_target_address_not_whitelisted() {
277 let mut request = create_basic_request();
278 request.to = Some("0xNOTWHITELISTED123456789".to_string());
279
280 let mut relayer = create_test_relayer(false, false);
281
282 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
283 evm_policy.whitelist_receivers = Some(vec![
284 "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
285 ]);
286 }
287
288 let result = validate_target_address(&request, &relayer);
289 assert!(result.is_err());
290 assert!(matches!(result, Err(ApiError::BadRequest(_))));
291 }
292
293 #[test]
294 fn test_validate_target_address_zero_address() {
295 let mut request = create_basic_request();
296 request.to = Some(ZERO_ADDRESS.to_string());
297 let relayer = create_test_relayer(false, false);
298
299 assert!(validate_target_address(&request, &relayer).is_ok());
300 }
301
302 #[test]
303 fn test_validate_target_address_relayer_address() {
304 let mut request = create_basic_request();
305 let relayer = create_test_relayer(false, false);
306 request.to = Some(relayer.address.clone());
307
308 assert!(validate_target_address(&request, &relayer).is_ok());
309 }
310
311 #[test]
312 fn test_validate_evm_transaction_request_gas_limit_too_low() {
313 let mut request = create_basic_request();
314 request.gas_limit = Some(20000);
315 let result = validate_evm_transaction_request(&request, &create_test_relayer(false, false));
316 assert!(result.is_err());
317
318 if let Err(ApiError::BadRequest(msg)) = result {
319 assert_eq!(
320 msg,
321 "gas_limit is too low, intrinsic gas is 21000 and gas_limit is 20000".to_string()
322 );
323 } else {
324 panic!("Expected BadRequest error");
325 }
326 }
327
328 #[test]
329 fn test_validate_legacy_transaction() {
330 let request = create_basic_request();
331 assert!(
332 validate_evm_transaction_request(&request, &create_test_relayer(false, false)).is_ok()
333 );
334 }
335
336 #[test]
337 fn test_validate_eip1559_transaction() {
338 let mut request = create_basic_request();
339 request.max_fee_per_gas = Some(30000000000);
340 request.max_priority_fee_per_gas = Some(20000000000);
341
342 assert!(
343 validate_evm_transaction_request(&request, &create_test_relayer(false, false)).is_ok()
344 );
345 }
346
347 #[test]
348 fn test_validate_eip1559_invalid_fees() {
349 let mut request = create_basic_request();
350 request.max_fee_per_gas = Some(20000000000);
351 request.max_priority_fee_per_gas = Some(30000000000); let relayer = create_test_relayer(false, false);
353 let result = validate_price_params(&request, &relayer);
354 assert!(result.is_err());
355 assert!(matches!(result, Err(ApiError::BadRequest(_))));
356 }
357 #[test]
358 fn test_validate_speed_transaction() {
359 let mut request = create_basic_request();
360 request.speed = Some(Speed::Fast);
361
362 assert!(
363 validate_evm_transaction_request(&request, &create_test_relayer(false, false)).is_ok()
364 );
365 }
366
367 #[test]
368 fn test_validate_missing_required_fields() {
369 let mut request = create_basic_request();
370 request.to = None;
371 request.data = None;
372
373 let result = validate_evm_transaction_request(&request, &create_test_relayer(false, false));
374 assert!(result.is_err());
375 assert!(matches!(result, Err(ApiError::BadRequest(_))));
376 }
377
378 #[test]
379 fn test_validate_invalid_valid_until_format() {
380 let mut request = create_basic_request();
381 request.valid_until = Some("invalid-date-format".to_string());
382
383 let result = validate_evm_transaction_request(&request, &create_test_relayer(false, false));
384 assert!(result.is_err());
385 assert!(matches!(result, Err(ApiError::BadRequest(_))));
386 }
387
388 #[test]
389 fn test_validate_whitelisted_address() {
390 let request = create_basic_request();
391 let mut relayer = create_test_relayer(false, false);
392
393 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
394 evm_policy.whitelist_receivers = Some(vec![
395 "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
396 ]);
397 }
398
399 assert!(validate_target_address(&request, &relayer).is_ok());
400 }
401
402 #[test]
403 fn test_validate_non_whitelisted_address() {
404 let mut request = create_basic_request();
405 request.to = Some("0x1234567890123456789012345678901234567890".to_string());
406 let mut relayer = create_test_relayer(false, false);
407
408 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
409 evm_policy.whitelist_receivers = Some(vec![
410 "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
411 ]);
412 }
413
414 let result = validate_target_address(&request, &relayer);
415 assert!(result.is_err());
416 assert!(matches!(result, Err(ApiError::BadRequest(_))));
417 }
418
419 #[test]
420 fn test_validate_mixed_transaction_types() {
421 let mut request = create_basic_request();
422 request.gas_price = Some(20000000000);
423 request.max_fee_per_gas = Some(30000000000);
424
425 let relayer = create_test_relayer(false, false);
426 let result = validate_price_params(&request, &relayer);
427 assert!(result.is_err());
428 assert!(matches!(result, Err(ApiError::BadRequest(_))));
429 }
430
431 #[test]
432 fn test_validate_incomplete_eip1559() {
433 let mut request = create_basic_request();
434 request.max_fee_per_gas = Some(30000000000);
435 let relayer = create_test_relayer(false, false);
438 let result = validate_price_params(&request, &relayer);
439 assert!(result.is_err());
440 assert!(matches!(result, Err(ApiError::BadRequest(_))));
441 }
442
443 #[test]
444 fn test_validate_invalid_eip1559_fees() {
445 let mut request = create_basic_request();
446 request.max_fee_per_gas = Some(20000000000);
447 request.max_priority_fee_per_gas = Some(30000000000); let relayer = create_test_relayer(false, false);
449 let result = validate_price_params(&request, &relayer);
450 assert!(result.is_err());
451 assert!(matches!(result, Err(ApiError::BadRequest(_))));
452 }
453
454 #[test]
455 fn test_validate_speed_with_gas_price() {
456 let mut request = create_basic_request();
457 request.speed = Some(Speed::Fast);
458 request.gas_price = Some(20000000000);
459 let relayer = create_test_relayer(false, false);
460 let result = validate_price_params(&request, &relayer);
461 assert!(result.is_err());
462 assert!(matches!(result, Err(ApiError::BadRequest(_))));
463 }
464
465 #[test]
466 fn test_validate_gas_price_cap() {
467 let mut request = create_basic_request();
468 request.gas_price = Some(20000000000);
469 let mut relayer = create_test_relayer(false, false);
470 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
471 evm_policy.gas_price_cap = Some(10000000000);
472 }
473 let result = validate_price_params(&request, &relayer);
474 assert!(result.is_err());
475 assert!(matches!(result, Err(ApiError::BadRequest(_))));
476 }
477
478 #[test]
479 fn test_validate_gas_limit_optional_when_estimation_enabled() {
480 let mut request = create_basic_request();
481 request.gas_limit = None; let relayer = create_test_relayer(false, false);
485 let result = validate_evm_transaction_request(&request, &relayer);
488 assert!(
489 result.is_ok(),
490 "gas_limit should be optional when gas_limit_estimation is enabled"
491 );
492 }
493
494 #[test]
495 fn test_validate_gas_limit_optional_when_estimation_explicitly_enabled() {
496 let mut request = create_basic_request();
497 request.gas_limit = None; let mut relayer = create_test_relayer(false, false);
501 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
502 evm_policy.gas_limit_estimation = Some(true);
503 }
504
505 let result = validate_evm_transaction_request(&request, &relayer);
506 assert!(
507 result.is_ok(),
508 "gas_limit should be optional when gas_limit_estimation is explicitly enabled"
509 );
510 }
511
512 #[test]
513 fn test_validate_gas_limit_required_when_estimation_disabled() {
514 let mut request = create_basic_request();
515 request.gas_limit = None; let mut relayer = create_test_relayer(false, false);
519 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
520 evm_policy.gas_limit_estimation = Some(false);
521 }
522
523 let result = validate_evm_transaction_request(&request, &relayer);
524 assert!(
525 result.is_err(),
526 "gas_limit should be required when gas_limit_estimation is disabled"
527 );
528
529 if let Err(ApiError::BadRequest(msg)) = result {
530 assert!(
531 msg.contains("gas_limit is required when gas_limit_estimation policy is disabled"),
532 "Expected specific error message, got: {}",
533 msg
534 );
535 } else {
536 panic!("Expected BadRequest error");
537 }
538 }
539
540 #[test]
541 fn test_validate_gas_limit_provided_when_estimation_disabled() {
542 let mut request = create_basic_request();
543 request.gas_limit = Some(21000); let mut relayer = create_test_relayer(false, false);
547 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
548 evm_policy.gas_limit_estimation = Some(false);
549 }
550
551 let result = validate_evm_transaction_request(&request, &relayer);
552 assert!(
553 result.is_ok(),
554 "validation should pass when gas_limit is provided and estimation is disabled"
555 );
556 }
557
558 #[test]
559 fn test_validate_gas_limit_provided_when_estimation_enabled() {
560 let mut request = create_basic_request();
561 request.gas_limit = Some(21000); let mut relayer = create_test_relayer(false, false);
565 if let RelayerNetworkPolicy::Evm(ref mut evm_policy) = relayer.policies {
566 evm_policy.gas_limit_estimation = Some(true);
567 }
568
569 let result = validate_evm_transaction_request(&request, &relayer);
570 assert!(
571 result.is_ok(),
572 "validation should pass when gas_limit is provided even when estimation is enabled"
573 );
574 }
575}