1use std::{collections::HashMap, env, fmt::Debug, str::FromStr};
2
3use alloy::{
4 primitives::{Address, Bytes, Keccak256, U256},
5 providers::{Provider, ProviderBuilder},
6 sol_types::SolValue,
7 transports::{RpcError, TransportErrorKind},
8};
9use hex::FromHex;
10use num_bigint::BigInt;
11use revm::{
12 state::{AccountInfo, Bytecode},
13 DatabaseRef,
14};
15use serde_json::Value;
16use tycho_common::simulation::errors::SimulationError;
17
18use crate::evm::{
19 engine_db::engine_db_interface::EngineDatabaseInterface,
20 simulation::{SimulationEngine, SimulationEngineError, SimulationParameters},
21 ContractCompiler, SlotId,
22};
23
24pub(crate) fn coerce_error(
25 err: &SimulationEngineError,
26 pool_state: &str,
27 gas_limit: Option<u64>,
28) -> SimulationError {
29 match err {
30 SimulationEngineError::TransactionError { ref data, ref gas_used }
32 if data.starts_with("0x") =>
33 {
34 let reason = parse_solidity_error_message(data);
35 let err = SimulationEngineError::TransactionError {
36 data: format!("Revert! Reason: {reason}"),
37 gas_used: *gas_used,
38 };
39
40 if let (Some(gas_limit), Some(gas_used)) = (gas_limit, gas_used) {
42 let usage = *gas_used as f64 / gas_limit as f64;
44 if usage >= 0.97 {
45 return SimulationError::InvalidInput(
46 format!(
47 "SimulationError: Likely out-of-gas. Used: {:.2}% of gas limit. \
48 Original error: {}. \
49 Pool state: {}",
50 usage * 100.0,
51 err,
52 pool_state,
53 ),
54 None,
55 );
56 }
57 }
58 SimulationError::FatalError(format!("Simulation reverted for unknown reason: {reason}"))
59 }
60 SimulationEngineError::TransactionError { ref data, ref gas_used }
62 if data.contains("OutOfGas") =>
63 {
64 let usage_msg = if let (Some(gas_limit), Some(gas_used)) = (gas_limit, gas_used) {
65 let usage = *gas_used as f64 / gas_limit as f64;
66 format!("Used: {:.2}% of gas limit. ", usage * 100.0)
67 } else {
68 String::new()
69 };
70
71 SimulationError::InvalidInput(
72 format!(
73 "SimulationError: out-of-gas. {usage_msg} Original error: {data}. Pool state: {pool_state}"
74 ),
75 None,
76 )
77 }
78 SimulationEngineError::TransactionError { ref data, .. } => {
79 SimulationError::FatalError(format!("TransactionError: {data}"))
80 }
81 SimulationEngineError::StorageError(message) => {
82 SimulationError::RecoverableError(message.clone())
83 }
84 _ => SimulationError::FatalError(err.clone().to_string()), }
87}
88
89fn parse_solidity_error_message(data: &str) -> String {
90 if data.len() >= 10 {
92 let data_bytes = match Vec::from_hex(&data[2..]) {
93 Ok(bytes) => bytes,
94 Err(_) => return format!("Failed to decode: {data}"),
95 };
96
97 if data_bytes.starts_with(&[0x08, 0xc3, 0x79, 0xa0]) {
100 if let Ok(decoded) = String::abi_decode(&data_bytes[4..]) {
101 return decoded;
102 }
103
104 } else if data_bytes.starts_with(&[0x4e, 0x48, 0x7b, 0x71]) {
106 if let Ok(decoded) = U256::abi_decode(&data_bytes[4..]) {
107 let panic_codes = get_solidity_panic_codes();
108 return panic_codes
109 .get(&decoded.as_limbs()[0])
110 .cloned()
111 .unwrap_or_else(|| format!("Panic({decoded})"));
112 }
113 }
114
115 if let Ok(decoded) = String::abi_decode(&data_bytes) {
117 return decoded;
118 }
119
120 if let Ok(decoded) = String::abi_decode(&data_bytes[4..]) {
122 return decoded;
123 }
124 }
125 format!("Failed to decode: {data}")
127}
128
129pub fn get_storage_slot_index_at_key(
177 key: Address,
178 mapping_slot: SlotId,
179 compiler: ContractCompiler,
180) -> SlotId {
181 let mut key_bytes = key.as_slice().to_vec();
182 if key_bytes.len() < 32 {
183 let padding = vec![0u8; 32 - key_bytes.len()];
184 key_bytes.splice(0..0, padding); }
186
187 let mapping_slot_bytes: [u8; 32] = mapping_slot.to_be_bytes();
188 compiler.compute_map_slot(&mapping_slot_bytes, &key_bytes)
189}
190
191fn get_solidity_panic_codes() -> HashMap<u64, String> {
192 let mut panic_codes = HashMap::new();
193 panic_codes.insert(0, "GenericCompilerPanic".to_string());
194 panic_codes.insert(1, "AssertionError".to_string());
195 panic_codes.insert(17, "ArithmeticOver/Underflow".to_string());
196 panic_codes.insert(18, "ZeroDivisionError".to_string());
197 panic_codes.insert(33, "UnknownEnumMember".to_string());
198 panic_codes.insert(34, "BadStorageByteArrayEncoding".to_string());
199 panic_codes.insert(51, "EmptyArray".to_string());
200 panic_codes.insert(0x32, "OutOfBounds".to_string());
201 panic_codes.insert(0x41, "OutOfMemory".to_string());
202 panic_codes.insert(0x51, "BadFunctionPointer".to_string());
203 panic_codes
204}
205
206pub(crate) async fn get_code_for_contract(
228 address: &str,
229 connection_string: Option<String>,
230) -> Result<Bytecode, SimulationError> {
231 let connection_string = connection_string.or_else(|| env::var("RPC_URL").ok());
233
234 let connection_string = match connection_string {
235 Some(url) => url,
236 None => {
237 return Err(SimulationError::FatalError(
238 "RPC_URL environment variable is not set".to_string(),
239 ))
240 }
241 };
242
243 let addr = Address::from_str(address)
244 .map_err(|_| SimulationError::FatalError(format!("Invalid address format: {address}")))?;
245 match sync_get_code(&connection_string, addr) {
247 Ok(code) if code.is_empty() => {
248 Err(SimulationError::FatalError("Empty code response from RPC".to_string()))
249 }
250 Ok(code) => {
251 let bytecode = Bytecode::new_raw(Bytes::from(code.to_vec()));
252 Ok(bytecode)
253 }
254 Err(e) => match e {
255 RpcError::Transport(err) => Err(SimulationError::RecoverableError(format!(
256 "Failed to get code for contract due to internal RPC error: {err:?}"
257 ))),
258 _ => Err(SimulationError::FatalError(format!(
259 "Failed to get code for contract. Invalid response from RPC: {e:?}"
260 ))),
261 },
262 }
263}
264
265fn sync_get_code(
266 connection_string: &str,
267 addr: Address,
268) -> Result<Bytes, RpcError<TransportErrorKind>> {
269 tokio::task::block_in_place(|| {
270 tokio::runtime::Handle::current().block_on(async {
271 let provider = ProviderBuilder::new()
273 .connect(connection_string)
274 .await?;
275 provider.get_code_at(addr).await
276 })
277 })
278}
279
280pub fn string_to_bytes32(pool_id: &str) -> Result<[u8; 32], SimulationError> {
310 let pool_id_no_prefix =
311 if let Some(stripped) = pool_id.strip_prefix("0x") { stripped } else { pool_id };
312 let bytes = hex::decode(pool_id_no_prefix)
313 .map_err(|e| SimulationError::FatalError(format!("Invalid hex string: {e}")))?;
314 if bytes.len() > 32 {
315 return Err(SimulationError::FatalError(format!(
316 "Hex string exceeds 32 bytes: length {}",
317 bytes.len()
318 )));
319 }
320 let mut array = [0u8; 32];
321 array[..bytes.len()].copy_from_slice(&bytes);
322 Ok(array)
323}
324
325pub fn json_deserialize_address_list(input: &[u8]) -> Result<Vec<Vec<u8>>, SimulationError> {
356 let json_value: Value = serde_json::from_slice(input)
357 .map_err(|_| SimulationError::FatalError(format!("Invalid JSON: {input:?}")))?;
358
359 if let Value::Array(hex_strings) = json_value {
360 let mut result = Vec::new();
361
362 for val in hex_strings {
363 if let Value::String(hexstring) = val {
364 let bytes = hex::decode(hexstring.trim_start_matches("0x")).map_err(|_| {
365 SimulationError::FatalError(format!("Invalid hex string: {hexstring}"))
366 })?;
367 result.push(bytes);
368 } else {
369 return Err(SimulationError::FatalError("Array contains a non-string value".into()));
370 }
371 }
372
373 Ok(result)
374 } else {
375 Err(SimulationError::FatalError("Input is not a JSON array".into()))
376 }
377}
378
379pub fn json_deserialize_be_bigint_list(input: &[u8]) -> Result<Vec<BigInt>, SimulationError> {
411 let json_value: Value = serde_json::from_slice(input)
412 .map_err(|_| SimulationError::FatalError(format!("Invalid JSON: {input:?}")))?;
413
414 if let Value::Array(hex_strings) = json_value {
415 let mut result = Vec::new();
416
417 for val in hex_strings {
418 if let Value::String(hexstring) = val {
419 let bytes = hex::decode(hexstring.trim_start_matches("0x")).map_err(|_| {
420 SimulationError::FatalError(format!("Invalid hex string: {hexstring}"))
421 })?;
422 let bigint = BigInt::from_signed_bytes_be(&bytes);
423 result.push(bigint);
424 } else {
425 return Err(SimulationError::FatalError("Array contains a non-string value".into()));
426 }
427 }
428
429 Ok(result)
430 } else {
431 Err(SimulationError::FatalError("Input is not a JSON array".into()))
432 }
433}
434
435pub(crate) async fn load_stateless_contracts<D: EngineDatabaseInterface + Clone + Debug>(
444 engine: &SimulationEngine<D>,
445 attributes: &HashMap<String, tycho_common::Bytes>,
446) -> Result<(), SimulationError>
447where
448 <D as DatabaseRef>::Error: Debug,
449 <D as EngineDatabaseInterface>::Error: Debug,
450{
451 let mut index = 0;
452 while let Some(encoded) = attributes.get(&format!("stateless_contract_addr_{index}")) {
453 let address = String::from_utf8(encoded.to_vec()).map_err(|e| {
454 SimulationError::FatalError(format!("stateless contract address is not UTF-8: {e}"))
455 })?;
456 let inline_code = attributes
457 .get(&format!("stateless_contract_code_{index}"))
458 .map(|value| value.to_vec());
459 index += 1;
460
461 let (account, code) = match inline_code {
462 Some(bytecode) => (address, Bytecode::new_raw(bytecode.into())),
463 None => {
464 let resolved = if address.starts_with("call") {
465 resolve_call_address(engine, &address)?
466 } else {
467 address
468 };
469 let code = get_code_for_contract(&resolved, None).await?;
470 (resolved, code)
471 }
472 };
473 let account: Address = account.parse().map_err(|_| {
474 SimulationError::FatalError(format!(
475 "stateless contract has an invalid address {account}"
476 ))
477 })?;
478 engine
479 .state
480 .init_account(
481 account,
482 AccountInfo {
483 balance: U256::ZERO,
484 nonce: 0,
485 code_hash: code.hash_slow(),
486 code: Some(code),
487 },
488 None,
489 false,
490 )
491 .map_err(|e| {
492 SimulationError::FatalError(format!(
493 "stateless contract init_account failed: {e:?}"
494 ))
495 })?;
496 }
497 Ok(())
498}
499
500pub(crate) fn resolve_call_address<D: EngineDatabaseInterface + Clone + Debug>(
503 engine: &SimulationEngine<D>,
504 directive: &str,
505) -> Result<String, SimulationError>
506where
507 <D as DatabaseRef>::Error: Debug,
508 <D as EngineDatabaseInterface>::Error: Debug,
509{
510 let method = directive
511 .split(':')
512 .next_back()
513 .ok_or_else(|| {
514 SimulationError::FatalError(format!("malformed stateless call directive {directive}"))
515 })?;
516 let to: Address = directive
517 .split(':')
518 .nth(1)
519 .ok_or_else(|| {
520 SimulationError::FatalError(format!(
521 "stateless call directive is missing its target {directive}"
522 ))
523 })?
524 .parse()
525 .map_err(|_| {
526 SimulationError::FatalError(format!(
527 "stateless call directive has an invalid target {directive}"
528 ))
529 })?;
530 let mut hasher = Keccak256::new();
531 hasher.update(method.as_bytes());
532 let selector = hasher.finalize()[..4].to_vec();
533 let res = engine
534 .simulate(&SimulationParameters {
535 caller: Address::ZERO,
536 to,
537 data: selector,
538 value: U256::ZERO,
539 overrides: None,
540 gas_limit: None,
541 transient_storage: None,
542 block_overrides: None,
543 })
544 .map_err(|e| SimulationError::FatalError(format!("stateless call failed: {e}")))?;
545 let address = Address::abi_decode(res.result.as_ref())
546 .map_err(|e| SimulationError::FatalError(format!("stateless call decode failed: {e}")))?;
547 Ok(address.to_string())
548}
549
550#[cfg(test)]
551mod tests {
552 use dotenv::dotenv;
553
554 use super::*;
555 use crate::utils::hexstring_to_vec;
556
557 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
558 #[cfg_attr(not(feature = "network_tests"), ignore)]
559 async fn test_get_code_for_address() {
560 let rpc_url = env::var("RPC_URL").unwrap_or_else(|_| {
561 dotenv().expect("Missing .env file");
562 env::var("RPC_URL").expect("Missing RPC_URL in .env file")
563 });
564
565 let address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640";
566 let result = get_code_for_contract(address, Some(rpc_url)).await;
567
568 assert!(result.is_ok(), "Network call should not fail");
569
570 let code = result.unwrap();
571 assert!(!code.bytes().is_empty(), "Code should not be empty");
572 }
573
574 #[test]
575 fn test_maybe_coerce_error_revert_no_gas_info() {
576 let err = SimulationEngineError::TransactionError{
577 data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011496e76616c6964206f7065726174696f6e000000000000000000000000000000".to_string(),
578 gas_used: None
579 };
580
581 let result = coerce_error(&err, "test_pool", None);
582
583 if let SimulationError::FatalError(msg) = result {
584 assert!(msg.contains("Simulation reverted for unknown reason: Invalid operation"));
585 } else {
586 panic!("Expected SolidityError error");
587 }
588 }
589
590 #[test]
591 fn test_maybe_coerce_error_out_of_gas() {
592 let err = SimulationEngineError::TransactionError{
594 data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011496e76616c6964206f7065726174696f6e000000000000000000000000000000".to_string(),
595 gas_used: Some(980)
596 };
597
598 let result = coerce_error(&err, "test_pool", Some(1000));
599
600 if let SimulationError::InvalidInput(message, _partial_result) = result {
601 assert!(message.contains("Used: 98.00% of gas limit."));
602 assert!(message.contains("test_pool"));
603 } else {
604 panic!("Expected OutOfGas error");
605 }
606 }
607
608 #[test]
609 fn test_maybe_coerce_error_no_gas_limit_info() {
610 let err = SimulationEngineError::TransactionError {
612 data: "OutOfGas".to_string(),
613 gas_used: None,
614 };
615
616 let result = coerce_error(&err, "test_pool", None);
617
618 if let SimulationError::InvalidInput(message, _partial_result) = result {
619 assert!(message.contains("Original error: OutOfGas"));
620 assert!(message.contains("Pool state: test_pool"));
621 } else {
622 panic!("Expected RetryDifferentInput error");
623 }
624 }
625
626 #[test]
627 fn test_maybe_coerce_error_storage_error() {
628 let err = SimulationEngineError::StorageError("Storage error:".to_string());
629
630 let result = coerce_error(&err, "test_pool", None);
631
632 if let SimulationError::RecoverableError(message) = result {
633 assert_eq!(message, "Storage error:");
634 } else {
635 println!("{result:?}");
636 panic!("Expected RetryLater error");
637 }
638 }
639
640 #[test]
641 fn test_maybe_coerce_error_no_match() {
642 let err = SimulationEngineError::TransactionError {
644 data: "Some other error".to_string(),
645 gas_used: None,
646 };
647
648 let result = coerce_error(&err, "test_pool", None);
649
650 if let SimulationError::FatalError(message) = result {
651 assert_eq!(message, "TransactionError: Some other error");
652 } else {
653 panic!("Expected solidity error");
654 }
655 }
656
657 #[test]
658 fn test_parse_solidity_error_message_error_string() {
659 let data = "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e416d6f756e7420746f6f206c6f77000000000000000000000000000000000000";
661
662 let result = parse_solidity_error_message(data);
663
664 assert_eq!(result, "Amount too low");
665 }
666
667 #[test]
668 fn test_parse_solidity_error_message_panic_code() {
669 let data = "0x4e487b710000000000000000000000000000000000000000000000000000000000000001";
671
672 let result = parse_solidity_error_message(data);
673
674 assert_eq!(result, "AssertionError");
675 }
676
677 #[test]
678 fn test_parse_solidity_error_message_failed_to_decode() {
679 let data = "0x1234567890";
681
682 let result = parse_solidity_error_message(data);
683
684 assert!(result.contains("Failed to decode"));
685 }
686
687 #[test]
688 fn test_hexstring_to_vec() {
689 let hexstring = "0x68656c6c6f";
690 let expected = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f];
691 let result = hexstring_to_vec(hexstring).unwrap();
692 assert_eq!(result, expected);
693 }
694
695 #[test]
696 fn test_hexstring_to_vec_no_prefix() {
697 let hexstring = "68656c6c6f";
698 let expected = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f];
699 let result = hexstring_to_vec(hexstring).unwrap();
700 assert_eq!(result, expected);
701 }
702
703 #[test]
704 fn test_hexstring_to_vec_invalid_characters() {
705 let hexstring = "0x68656c6c6z"; let result = hexstring_to_vec(hexstring);
707 assert!(result.is_err());
708 if let Err(SimulationError::FatalError(msg)) = result {
709 assert!(msg.contains("Invalid hex string"));
710 } else {
711 panic!("Expected EncodingError");
712 }
713 }
714
715 #[test]
716 fn test_json_deserialize_address_list() {
717 let json_input = r#"["0x1234","0xabcd"]"#.as_bytes();
718 let result = json_deserialize_address_list(json_input).unwrap();
719 assert_eq!(result, vec![vec![0x12, 0x34], vec![0xab, 0xcd]]);
720 }
721
722 #[test]
723 fn test_json_deserialize_bigint_list() {
724 let json_input = r#"["0x0b1a2bc2ec500000","0x02c68af0bb140000"]"#.as_bytes();
725 let result = json_deserialize_be_bigint_list(json_input).unwrap();
726 assert_eq!(
727 result,
728 vec![BigInt::from(800000000000000000u64), BigInt::from(200000000000000000u64)]
729 );
730 }
731
732 #[test]
733 fn test_invalid_deserialize_address_list() {
734 let json_input = r#"["invalid_hex"]"#.as_bytes();
735 let result = json_deserialize_address_list(json_input);
736 assert!(result.is_err());
737 }
738
739 #[test]
740 fn test_invalid_deserialize_bigint_list() {
741 let json_input = r#"["invalid_hex"]"#.as_bytes();
742 let result = json_deserialize_be_bigint_list(json_input);
743 assert!(result.is_err());
744 }
745}