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 }
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}