1use crate::constants::{
2 COMPLEX_GAS_LIMIT, DEFAULT_GAS_LIMIT, DEFAULT_TRANSACTION_SPEED, ERC20_TRANSFER_GAS_LIMIT,
3 ERC721_TRANSFER_GAS_LIMIT, GAS_TX_CREATE_CONTRACT, GAS_TX_DATA_NONZERO, GAS_TX_DATA_ZERO,
4};
5use crate::models::evm::Speed;
6use crate::models::{EvmTransactionData, EvmTransactionRequest};
7use crate::utils::time::minutes_ms;
8
9pub fn get_resubmit_timeout_for_speed(speed: &Option<Speed>) -> i64 {
17 let speed_value = speed.clone().unwrap_or(DEFAULT_TRANSACTION_SPEED);
18
19 match speed_value {
20 Speed::SafeLow => minutes_ms(10),
21 Speed::Average => minutes_ms(5),
22 Speed::Fast => minutes_ms(3),
23 Speed::Fastest => minutes_ms(2),
24 }
25}
26
27pub fn get_resubmit_timeout_with_backoff(timeout: i64, attempts: usize) -> i64 {
36 if attempts <= 1 {
37 timeout
38 } else {
39 timeout * 2_i64.pow((attempts - 1) as u32)
40 }
41}
42
43pub fn get_evm_default_gas_limit_for_tx(tx: &EvmTransactionData) -> u64 {
51 if tx.data.is_none() {
52 DEFAULT_GAS_LIMIT
53 } else if tx.data.as_ref().unwrap().starts_with("0xa9059cbb") {
54 ERC20_TRANSFER_GAS_LIMIT
55 } else if tx.data.as_ref().unwrap().starts_with("0x23b872dd") {
56 ERC721_TRANSFER_GAS_LIMIT
57 } else {
58 COMPLEX_GAS_LIMIT
59 }
60}
61
62pub fn calculate_intrinsic_gas(tx: &EvmTransactionRequest) -> u64 {
70 let base_gas = if tx.to.is_none() {
71 GAS_TX_CREATE_CONTRACT
72 } else {
73 DEFAULT_GAS_LIMIT
74 };
75
76 let data_gas = match &tx.data {
77 Some(data_str) => {
78 let hex_str = data_str.strip_prefix("0x").unwrap_or(data_str);
79 hex::decode(hex_str)
80 .map(|bytes| calculate_data_gas(&bytes))
81 .unwrap_or(0)
82 }
83 None => 0,
84 };
85
86 base_gas + data_gas
87}
88
89fn calculate_data_gas(data: &[u8]) -> u64 {
97 let mut gas = 0;
98 for &byte in data {
99 if byte == 0 {
100 gas += GAS_TX_DATA_ZERO;
101 } else {
102 gas += GAS_TX_DATA_NONZERO;
103 }
104 }
105 gas
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::models::evm::Speed;
112 use crate::models::EvmTransactionData;
113
114 #[test]
115 fn test_get_resubmit_timeout_for_speed() {
116 assert_eq!(
118 get_resubmit_timeout_for_speed(&Some(Speed::SafeLow)),
119 minutes_ms(10)
120 );
121 assert_eq!(
122 get_resubmit_timeout_for_speed(&Some(Speed::Average)),
123 minutes_ms(5)
124 );
125 assert_eq!(
126 get_resubmit_timeout_for_speed(&Some(Speed::Fast)),
127 minutes_ms(3)
128 );
129 assert_eq!(
130 get_resubmit_timeout_for_speed(&Some(Speed::Fastest)),
131 minutes_ms(2)
132 );
133
134 assert_eq!(
136 get_resubmit_timeout_for_speed(&None),
137 minutes_ms(3) );
139 }
140
141 #[test]
142 fn test_get_resubmit_timeout_with_backoff() {
143 let base_timeout = 300000; assert_eq!(get_resubmit_timeout_with_backoff(base_timeout, 1), 300000);
147
148 assert_eq!(get_resubmit_timeout_with_backoff(base_timeout, 2), 600000);
150
151 assert_eq!(get_resubmit_timeout_with_backoff(base_timeout, 3), 1200000);
153
154 assert_eq!(get_resubmit_timeout_with_backoff(base_timeout, 4), 2400000);
156
157 assert_eq!(get_resubmit_timeout_with_backoff(base_timeout, 0), 300000);
159 }
160
161 #[test]
162 fn test_get_evm_default_gas_limit_for_tx_no_data() {
163 let tx = EvmTransactionData {
164 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
165 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
166 value: crate::models::U256::from(1000000000000000000u128),
167 data: None,
168 gas_limit: None,
169 gas_price: Some(20_000_000_000),
170 nonce: Some(1),
171 chain_id: 1,
172 hash: None,
173 signature: None,
174 speed: Some(Speed::Average),
175 max_fee_per_gas: None,
176 max_priority_fee_per_gas: None,
177 raw: None,
178 };
179
180 assert_eq!(get_evm_default_gas_limit_for_tx(&tx), DEFAULT_GAS_LIMIT);
181 }
182
183 #[test]
184 fn test_get_evm_default_gas_limit_for_tx_erc20_transfer() {
185 let tx = EvmTransactionData {
186 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
187 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
188 value: crate::models::U256::from(0u128),
189 data: Some("0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000de0b6b3a7640000".to_string()),
190 gas_limit: None,
191 gas_price: Some(20_000_000_000),
192 nonce: Some(1),
193 chain_id: 1,
194 hash: None,
195 signature: None,
196 speed: Some(Speed::Average),
197 max_fee_per_gas: None,
198 max_priority_fee_per_gas: None,
199 raw: None,
200 };
201
202 assert_eq!(
203 get_evm_default_gas_limit_for_tx(&tx),
204 ERC20_TRANSFER_GAS_LIMIT
205 );
206 }
207
208 #[test]
209 fn test_get_evm_default_gas_limit_for_tx_transfer_from() {
210 let tx = EvmTransactionData {
211 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
212 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
213 value: crate::models::U256::from(0u128),
214 data: Some("0x23b872dd000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed0000000000000000000000000000000000000000000000000de0b6b3a7640000".to_string()),
215 gas_limit: None,
216 gas_price: Some(20_000_000_000),
217 nonce: Some(1),
218 chain_id: 1,
219 hash: None,
220 signature: None,
221 speed: Some(Speed::Average),
222 max_fee_per_gas: None,
223 max_priority_fee_per_gas: None,
224 raw: None,
225 };
226
227 assert_eq!(
228 get_evm_default_gas_limit_for_tx(&tx),
229 ERC721_TRANSFER_GAS_LIMIT
230 );
231 }
232
233 #[test]
234 fn test_get_evm_default_gas_limit_for_tx_complex_transaction() {
235 let tx = EvmTransactionData {
236 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
237 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
238 value: crate::models::U256::from(0u128),
239 data: Some("0x095ea7b3000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000de0b6b3a7640000".to_string()),
240 gas_limit: None,
241 gas_price: Some(20_000_000_000),
242 nonce: Some(1),
243 chain_id: 1,
244 hash: None,
245 signature: None,
246 speed: Some(Speed::Average),
247 max_fee_per_gas: None,
248 max_priority_fee_per_gas: None,
249 raw: None,
250 };
251
252 assert_eq!(get_evm_default_gas_limit_for_tx(&tx), COMPLEX_GAS_LIMIT);
253 }
254
255 #[test]
256 fn test_get_evm_default_gas_limit_for_tx_empty_data() {
257 let tx = EvmTransactionData {
258 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
259 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
260 value: crate::models::U256::from(1000000000000000000u128),
261 data: Some("0x".to_string()),
262 gas_limit: None,
263 gas_price: Some(20_000_000_000),
264 nonce: Some(1),
265 chain_id: 1,
266 hash: None,
267 signature: None,
268 speed: Some(Speed::Average),
269 max_fee_per_gas: None,
270 max_priority_fee_per_gas: None,
271 raw: None,
272 };
273
274 assert_eq!(get_evm_default_gas_limit_for_tx(&tx), COMPLEX_GAS_LIMIT);
275 }
276
277 #[test]
278 fn test_get_evm_default_gas_limit_for_tx_malformed_data() {
279 let tx = EvmTransactionData {
280 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
281 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
282 value: crate::models::U256::from(0u128),
283 data: Some("0xa9059c".to_string()), gas_limit: None,
285 gas_price: Some(20_000_000_000),
286 nonce: Some(1),
287 chain_id: 1,
288 hash: None,
289 signature: None,
290 speed: Some(Speed::Average),
291 max_fee_per_gas: None,
292 max_priority_fee_per_gas: None,
293 raw: None,
294 };
295
296 assert_eq!(get_evm_default_gas_limit_for_tx(&tx), COMPLEX_GAS_LIMIT);
297 }
298
299 #[test]
300 fn test_get_evm_default_gas_limit_for_tx_partial_signature_match() {
301 let tx = EvmTransactionData {
303 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
304 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
305 value: crate::models::U256::from(0u128),
306 data: Some("0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000001".to_string()),
307 gas_limit: None,
308 gas_price: Some(20_000_000_000),
309 nonce: Some(1),
310 chain_id: 1,
311 hash: None,
312 signature: None,
313 speed: Some(Speed::Average),
314 max_fee_per_gas: None,
315 max_priority_fee_per_gas: None,
316 raw: None,
317 };
318
319 assert_eq!(
321 get_evm_default_gas_limit_for_tx(&tx),
322 ERC20_TRANSFER_GAS_LIMIT
323 );
324 }
325
326 #[test]
327 fn test_get_evm_default_gas_limit_for_tx_case_sensitivity() {
328 let tx = EvmTransactionData {
330 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
331 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
332 value: crate::models::U256::from(0u128),
333 data: Some("0xA9059CBB000000000000000000000000742D35CC6634C0532925A3B844BC454E4438F44E0000000000000000000000000000000000000000000000000DE0B6B3A7640000".to_string()),
334 gas_limit: None,
335 gas_price: Some(20_000_000_000),
336 nonce: Some(1),
337 chain_id: 1,
338 hash: None,
339 signature: None,
340 speed: Some(Speed::Average),
341 max_fee_per_gas: None,
342 max_priority_fee_per_gas: None,
343 raw: None,
344 };
345
346 assert_eq!(get_evm_default_gas_limit_for_tx(&tx), COMPLEX_GAS_LIMIT);
348 }
349
350 #[test]
351 fn test_calculate_data_gas_empty_data() {
352 let data = &[];
353 assert_eq!(calculate_data_gas(data), 0);
354 }
355
356 #[test]
357 fn test_calculate_data_gas_all_zero_bytes() {
358 let data = &[0x00, 0x00, 0x00, 0x00];
359 assert_eq!(calculate_data_gas(data), 4 * GAS_TX_DATA_ZERO);
361 }
362
363 #[test]
364 fn test_calculate_data_gas_all_nonzero_bytes() {
365 let data = &[0x01, 0x02, 0x03, 0x04];
366 assert_eq!(calculate_data_gas(data), 4 * GAS_TX_DATA_NONZERO);
368 }
369
370 #[test]
371 fn test_calculate_data_gas_mixed_bytes() {
372 let data = &[0x00, 0x01, 0x00, 0x02, 0x03, 0x00];
373 assert_eq!(
375 calculate_data_gas(data),
376 3 * GAS_TX_DATA_ZERO + 3 * GAS_TX_DATA_NONZERO
377 );
378 }
379
380 #[test]
381 fn test_calculate_intrinsic_gas_regular_transaction_no_data() {
382 let tx = EvmTransactionRequest {
383 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
384 value: crate::models::U256::from(1000000000000000000u128),
385 data: None,
386 gas_limit: None,
387 gas_price: Some(20_000_000_000),
388 speed: Some(Speed::Average),
389 max_fee_per_gas: None,
390 max_priority_fee_per_gas: None,
391 valid_until: None,
392 };
393
394 assert_eq!(calculate_intrinsic_gas(&tx), DEFAULT_GAS_LIMIT);
395 }
396
397 #[test]
398 fn test_calculate_intrinsic_gas_contract_creation_no_data() {
399 let tx = EvmTransactionRequest {
400 to: None, value: crate::models::U256::from(0u128),
402 data: None,
403 gas_limit: None,
404 gas_price: Some(20_000_000_000),
405 speed: Some(Speed::Average),
406 max_fee_per_gas: None,
407 max_priority_fee_per_gas: None,
408 valid_until: None,
409 };
410
411 assert_eq!(calculate_intrinsic_gas(&tx), GAS_TX_CREATE_CONTRACT);
412 }
413
414 #[test]
415 fn test_calculate_intrinsic_gas_with_data() {
416 let tx = EvmTransactionRequest {
417 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
418 value: crate::models::U256::from(0u128),
419 data: Some("0x01020304".to_string()), gas_limit: None,
421 gas_price: Some(20_000_000_000),
422 speed: Some(Speed::Average),
423 max_fee_per_gas: None,
424 max_priority_fee_per_gas: None,
425 valid_until: None,
426 };
427
428 let expected_gas = DEFAULT_GAS_LIMIT + 4 * GAS_TX_DATA_NONZERO;
429 assert_eq!(calculate_intrinsic_gas(&tx), expected_gas);
430 }
431
432 #[test]
433 fn test_calculate_intrinsic_gas_with_hex_prefix() {
434 let tx = EvmTransactionRequest {
435 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
436 value: crate::models::U256::from(0u128),
437 data: Some("0x00010203".to_string()), gas_limit: None,
439 gas_price: Some(20_000_000_000),
440 speed: Some(Speed::Average),
441 max_fee_per_gas: None,
442 max_priority_fee_per_gas: None,
443 valid_until: None,
444 };
445
446 let expected_gas = DEFAULT_GAS_LIMIT + GAS_TX_DATA_ZERO + 3 * GAS_TX_DATA_NONZERO;
448 assert_eq!(calculate_intrinsic_gas(&tx), expected_gas);
449 }
450
451 #[test]
452 fn test_calculate_intrinsic_gas_without_hex_prefix() {
453 let tx = EvmTransactionRequest {
454 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
455 value: crate::models::U256::from(0u128),
456 data: Some("00010203".to_string()), gas_limit: None,
458 gas_price: Some(20_000_000_000),
459 speed: Some(Speed::Average),
460 max_fee_per_gas: None,
461 max_priority_fee_per_gas: None,
462 valid_until: None,
463 };
464
465 let expected_gas = DEFAULT_GAS_LIMIT + GAS_TX_DATA_ZERO + 3 * GAS_TX_DATA_NONZERO;
467 assert_eq!(calculate_intrinsic_gas(&tx), expected_gas);
468 }
469
470 #[test]
471 fn test_calculate_intrinsic_gas_invalid_hex_data() {
472 let tx = EvmTransactionRequest {
473 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
474 value: crate::models::U256::from(0u128),
475 data: Some("0xINVALID_HEX".to_string()), gas_limit: None,
477 gas_price: Some(20_000_000_000),
478 speed: Some(Speed::Average),
479 max_fee_per_gas: None,
480 max_priority_fee_per_gas: None,
481 valid_until: None,
482 };
483
484 assert_eq!(calculate_intrinsic_gas(&tx), DEFAULT_GAS_LIMIT);
486 }
487
488 #[test]
489 fn test_calculate_intrinsic_gas_empty_hex_data() {
490 let tx = EvmTransactionRequest {
491 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
492 value: crate::models::U256::from(0u128),
493 data: Some("0x".to_string()), gas_limit: None,
495 gas_price: Some(20_000_000_000),
496 speed: Some(Speed::Average),
497 max_fee_per_gas: None,
498 max_priority_fee_per_gas: None,
499 valid_until: None,
500 };
501
502 assert_eq!(calculate_intrinsic_gas(&tx), DEFAULT_GAS_LIMIT);
504 }
505
506 #[test]
507 fn test_calculate_intrinsic_gas_typical_erc20_transfer() {
508 let tx = EvmTransactionRequest {
509 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
510 value: crate::models::U256::from(0u128),
511 data: Some("0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000de0b6b3a7640000".to_string()),
512 gas_limit: None,
513 gas_price: Some(20_000_000_000),
514 speed: Some(Speed::Average),
515 max_fee_per_gas: None,
516 max_priority_fee_per_gas: None,
517 valid_until: None,
518 };
519
520 let data_bytes = hex::decode("a9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e0000000000000000000000000000000000000000000000000de0b6b3a7640000").unwrap();
521 let data_gas = calculate_data_gas(&data_bytes);
522 let expected_gas = DEFAULT_GAS_LIMIT + data_gas;
523
524 assert_eq!(calculate_intrinsic_gas(&tx), expected_gas);
525 }
526
527 #[test]
528 fn test_calculate_intrinsic_gas_large_data() {
529 let large_data = "0x".to_string() + &"01".repeat(1000);
531
532 let tx = EvmTransactionRequest {
533 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
534 value: crate::models::U256::from(0u128),
535 data: Some(large_data),
536 gas_limit: None,
537 gas_price: Some(20_000_000_000),
538 speed: Some(Speed::Average),
539 max_fee_per_gas: None,
540 max_priority_fee_per_gas: None,
541 valid_until: None,
542 };
543
544 let expected_gas = DEFAULT_GAS_LIMIT + 1000 * GAS_TX_DATA_NONZERO;
546 assert_eq!(calculate_intrinsic_gas(&tx), expected_gas);
547 }
548}