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