tycho_simulation/evm/protocol/vm/
utils.rs1use std::{collections::HashMap, env, str::FromStr};
2
3use alloy::{
4 primitives::{Address, Bytes, U256},
5 providers::{Provider, ProviderBuilder},
6 sol_types::SolValue,
7 transports::{RpcError, TransportErrorKind},
8};
9use hex::FromHex;
10use num_bigint::BigInt;
11use revm::state::Bytecode;
12use serde_json::Value;
13use tycho_common::simulation::errors::SimulationError;
14
15use crate::evm::{simulation::SimulationEngineError, ContractCompiler, SlotId};
16
17pub(crate) fn coerce_error(
18 err: &SimulationEngineError,
19 pool_state: &str,
20 gas_limit: Option<u64>,
21) -> SimulationError {
22 match err {
23 SimulationEngineError::TransactionError { ref data, ref gas_used }
25 if data.starts_with("0x") =>
26 {
27 let reason = parse_solidity_error_message(data);
28 let err = SimulationEngineError::TransactionError {
29 data: format!("Revert! Reason: {reason}"),
30 gas_used: *gas_used,
31 };
32
33 if let (Some(gas_limit), Some(gas_used)) = (gas_limit, gas_used) {
35 let usage = *gas_used as f64 / gas_limit as f64;
37 if usage >= 0.97 {
38 return SimulationError::InvalidInput(
39 format!(
40 "SimulationError: Likely out-of-gas. Used: {:.2}% of gas limit. \
41 Original error: {}. \
42 Pool state: {}",
43 usage * 100.0,
44 err,
45 pool_state,
46 ),
47 None,
48 );
49 }
50 }
51 SimulationError::FatalError(format!("Simulation reverted for unknown reason: {reason}"))
52 }
53 SimulationEngineError::TransactionError { ref data, ref gas_used }
55 if data.contains("OutOfGas") =>
56 {
57 let usage_msg = if let (Some(gas_limit), Some(gas_used)) = (gas_limit, gas_used) {
58 let usage = *gas_used as f64 / gas_limit as f64;
59 format!("Used: {:.2}% of gas limit. ", usage * 100.0)
60 } else {
61 String::new()
62 };
63
64 SimulationError::InvalidInput(
65 format!(
66 "SimulationError: out-of-gas. {usage_msg} Original error: {data}. Pool state: {pool_state}"
67 ),
68 None,
69 )
70 }
71 SimulationEngineError::TransactionError { ref data, .. } => {
72 SimulationError::FatalError(format!("TransactionError: {data}"))
73 }
74 SimulationEngineError::StorageError(message) => {
75 SimulationError::RecoverableError(message.clone())
76 }
77 _ => SimulationError::FatalError(err.clone().to_string()), }
80}
81
82fn parse_solidity_error_message(data: &str) -> String {
83 if data.len() >= 10 {
85 let data_bytes = match Vec::from_hex(&data[2..]) {
86 Ok(bytes) => bytes,
87 Err(_) => return format!("Failed to decode: {data}"),
88 };
89
90 if data_bytes.starts_with(&[0x08, 0xc3, 0x79, 0xa0]) {
93 if let Ok(decoded) = String::abi_decode(&data_bytes[4..]) {
94 return decoded;
95 }
96
97 } else if data_bytes.starts_with(&[0x4e, 0x48, 0x7b, 0x71]) {
99 if let Ok(decoded) = U256::abi_decode(&data_bytes[4..]) {
100 let panic_codes = get_solidity_panic_codes();
101 return panic_codes
102 .get(&decoded.as_limbs()[0])
103 .cloned()
104 .unwrap_or_else(|| format!("Panic({decoded})"));
105 }
106 }
107
108 if let Ok(decoded) = String::abi_decode(&data_bytes) {
110 return decoded;
111 }
112
113 if let Ok(decoded) = String::abi_decode(&data_bytes[4..]) {
115 return decoded;
116 }
117 }
118 format!("Failed to decode: {data}")
120}
121
122pub fn get_storage_slot_index_at_key(
170 key: Address,
171 mapping_slot: SlotId,
172 compiler: ContractCompiler,
173) -> SlotId {
174 let mut key_bytes = key.as_slice().to_vec();
175 if key_bytes.len() < 32 {
176 let padding = vec![0u8; 32 - key_bytes.len()];
177 key_bytes.splice(0..0, padding); }
179
180 let mapping_slot_bytes: [u8; 32] = mapping_slot.to_be_bytes();
181 compiler.compute_map_slot(&mapping_slot_bytes, &key_bytes)
182}
183
184fn get_solidity_panic_codes() -> HashMap<u64, String> {
185 let mut panic_codes = HashMap::new();
186 panic_codes.insert(0, "GenericCompilerPanic".to_string());
187 panic_codes.insert(1, "AssertionError".to_string());
188 panic_codes.insert(17, "ArithmeticOver/Underflow".to_string());
189 panic_codes.insert(18, "ZeroDivisionError".to_string());
190 panic_codes.insert(33, "UnknownEnumMember".to_string());
191 panic_codes.insert(34, "BadStorageByteArrayEncoding".to_string());
192 panic_codes.insert(51, "EmptyArray".to_string());
193 panic_codes.insert(0x32, "OutOfBounds".to_string());
194 panic_codes.insert(0x41, "OutOfMemory".to_string());
195 panic_codes.insert(0x51, "BadFunctionPointer".to_string());
196 panic_codes
197}
198
199pub(crate) async fn get_code_for_contract(
221 address: &str,
222 connection_string: Option<String>,
223) -> Result<Bytecode, SimulationError> {
224 let connection_string = connection_string.or_else(|| env::var("RPC_URL").ok());
226
227 let connection_string = match connection_string {
228 Some(url) => url,
229 None => {
230 return Err(SimulationError::FatalError(
231 "RPC_URL environment variable is not set".to_string(),
232 ))
233 }
234 };
235
236 let addr = Address::from_str(address)
237 .map_err(|_| SimulationError::FatalError(format!("Invalid address format: {address}")))?;
238 match sync_get_code(&connection_string, addr) {
240 Ok(code) if code.is_empty() => {
241 Err(SimulationError::FatalError("Empty code response from RPC".to_string()))
242 }
243 Ok(code) => {
244 let bytecode = Bytecode::new_raw(Bytes::from(code.to_vec()));
245 Ok(bytecode)
246 }
247 Err(e) => match e {
248 RpcError::Transport(err) => Err(SimulationError::RecoverableError(format!(
249 "Failed to get code for contract due to internal RPC error: {err:?}"
250 ))),
251 _ => Err(SimulationError::FatalError(format!(
252 "Failed to get code for contract. Invalid response from RPC: {e:?}"
253 ))),
254 },
255 }
256}
257
258fn sync_get_code(
259 connection_string: &str,
260 addr: Address,
261) -> Result<Bytes, RpcError<TransportErrorKind>> {
262 tokio::task::block_in_place(|| {
263 tokio::runtime::Handle::current().block_on(async {
264 let provider = ProviderBuilder::new()
266 .connect(connection_string)
267 .await?;
268 provider.get_code_at(addr).await
269 })
270 })
271}
272
273pub fn string_to_bytes32(pool_id: &str) -> Result<[u8; 32], SimulationError> {
303 let pool_id_no_prefix =
304 if let Some(stripped) = pool_id.strip_prefix("0x") { stripped } else { pool_id };
305 let bytes = hex::decode(pool_id_no_prefix)
306 .map_err(|e| SimulationError::FatalError(format!("Invalid hex string: {e}")))?;
307 if bytes.len() > 32 {
308 return Err(SimulationError::FatalError(format!(
309 "Hex string exceeds 32 bytes: length {}",
310 bytes.len()
311 )));
312 }
313 let mut array = [0u8; 32];
314 array[..bytes.len()].copy_from_slice(&bytes);
315 Ok(array)
316}
317
318pub fn json_deserialize_address_list(input: &[u8]) -> Result<Vec<Vec<u8>>, SimulationError> {
349 let json_value: Value = serde_json::from_slice(input)
350 .map_err(|_| SimulationError::FatalError(format!("Invalid JSON: {input:?}")))?;
351
352 if let Value::Array(hex_strings) = json_value {
353 let mut result = Vec::new();
354
355 for val in hex_strings {
356 if let Value::String(hexstring) = val {
357 let bytes = hex::decode(hexstring.trim_start_matches("0x")).map_err(|_| {
358 SimulationError::FatalError(format!("Invalid hex string: {hexstring}"))
359 })?;
360 result.push(bytes);
361 } else {
362 return Err(SimulationError::FatalError("Array contains a non-string value".into()));
363 }
364 }
365
366 Ok(result)
367 } else {
368 Err(SimulationError::FatalError("Input is not a JSON array".into()))
369 }
370}
371
372pub fn json_deserialize_be_bigint_list(input: &[u8]) -> Result<Vec<BigInt>, SimulationError> {
404 let json_value: Value = serde_json::from_slice(input)
405 .map_err(|_| SimulationError::FatalError(format!("Invalid JSON: {input:?}")))?;
406
407 if let Value::Array(hex_strings) = json_value {
408 let mut result = Vec::new();
409
410 for val in hex_strings {
411 if let Value::String(hexstring) = val {
412 let bytes = hex::decode(hexstring.trim_start_matches("0x")).map_err(|_| {
413 SimulationError::FatalError(format!("Invalid hex string: {hexstring}"))
414 })?;
415 let bigint = BigInt::from_signed_bytes_be(&bytes);
416 result.push(bigint);
417 } else {
418 return Err(SimulationError::FatalError("Array contains a non-string value".into()));
419 }
420 }
421
422 Ok(result)
423 } else {
424 Err(SimulationError::FatalError("Input is not a JSON array".into()))
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use dotenv::dotenv;
431
432 use super::*;
433 use crate::utils::hexstring_to_vec;
434
435 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
436 #[cfg_attr(not(feature = "network_tests"), ignore)]
437 async fn test_get_code_for_address() {
438 let rpc_url = env::var("RPC_URL").unwrap_or_else(|_| {
439 dotenv().expect("Missing .env file");
440 env::var("RPC_URL").expect("Missing RPC_URL in .env file")
441 });
442
443 let address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640";
444 let result = get_code_for_contract(address, Some(rpc_url)).await;
445
446 assert!(result.is_ok(), "Network call should not fail");
447
448 let code = result.unwrap();
449 assert!(!code.bytes().is_empty(), "Code should not be empty");
450 }
451
452 #[test]
453 fn test_maybe_coerce_error_revert_no_gas_info() {
454 let err = SimulationEngineError::TransactionError{
455 data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011496e76616c6964206f7065726174696f6e000000000000000000000000000000".to_string(),
456 gas_used: None
457 };
458
459 let result = coerce_error(&err, "test_pool", None);
460
461 if let SimulationError::FatalError(msg) = result {
462 assert!(msg.contains("Simulation reverted for unknown reason: Invalid operation"));
463 } else {
464 panic!("Expected SolidityError error");
465 }
466 }
467
468 #[test]
469 fn test_maybe_coerce_error_out_of_gas() {
470 let err = SimulationEngineError::TransactionError{
472 data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000011496e76616c6964206f7065726174696f6e000000000000000000000000000000".to_string(),
473 gas_used: Some(980)
474 };
475
476 let result = coerce_error(&err, "test_pool", Some(1000));
477
478 if let SimulationError::InvalidInput(message, _partial_result) = result {
479 assert!(message.contains("Used: 98.00% of gas limit."));
480 assert!(message.contains("test_pool"));
481 } else {
482 panic!("Expected OutOfGas error");
483 }
484 }
485
486 #[test]
487 fn test_maybe_coerce_error_no_gas_limit_info() {
488 let err = SimulationEngineError::TransactionError {
490 data: "OutOfGas".to_string(),
491 gas_used: None,
492 };
493
494 let result = coerce_error(&err, "test_pool", None);
495
496 if let SimulationError::InvalidInput(message, _partial_result) = result {
497 assert!(message.contains("Original error: OutOfGas"));
498 assert!(message.contains("Pool state: test_pool"));
499 } else {
500 panic!("Expected RetryDifferentInput error");
501 }
502 }
503
504 #[test]
505 fn test_maybe_coerce_error_storage_error() {
506 let err = SimulationEngineError::StorageError("Storage error:".to_string());
507
508 let result = coerce_error(&err, "test_pool", None);
509
510 if let SimulationError::RecoverableError(message) = result {
511 assert_eq!(message, "Storage error:");
512 } else {
513 println!("{result:?}");
514 panic!("Expected RetryLater error");
515 }
516 }
517
518 #[test]
519 fn test_maybe_coerce_error_no_match() {
520 let err = SimulationEngineError::TransactionError {
522 data: "Some other error".to_string(),
523 gas_used: None,
524 };
525
526 let result = coerce_error(&err, "test_pool", None);
527
528 if let SimulationError::FatalError(message) = result {
529 assert_eq!(message, "TransactionError: Some other error");
530 } else {
531 panic!("Expected solidity error");
532 }
533 }
534
535 #[test]
536 fn test_parse_solidity_error_message_error_string() {
537 let data = "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e416d6f756e7420746f6f206c6f77000000000000000000000000000000000000";
539
540 let result = parse_solidity_error_message(data);
541
542 assert_eq!(result, "Amount too low");
543 }
544
545 #[test]
546 fn test_parse_solidity_error_message_panic_code() {
547 let data = "0x4e487b710000000000000000000000000000000000000000000000000000000000000001";
549
550 let result = parse_solidity_error_message(data);
551
552 assert_eq!(result, "AssertionError");
553 }
554
555 #[test]
556 fn test_parse_solidity_error_message_failed_to_decode() {
557 let data = "0x1234567890";
559
560 let result = parse_solidity_error_message(data);
561
562 assert!(result.contains("Failed to decode"));
563 }
564
565 #[test]
566 fn test_hexstring_to_vec() {
567 let hexstring = "0x68656c6c6f";
568 let expected = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f];
569 let result = hexstring_to_vec(hexstring).unwrap();
570 assert_eq!(result, expected);
571 }
572
573 #[test]
574 fn test_hexstring_to_vec_no_prefix() {
575 let hexstring = "68656c6c6f";
576 let expected = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f];
577 let result = hexstring_to_vec(hexstring).unwrap();
578 assert_eq!(result, expected);
579 }
580
581 #[test]
582 fn test_hexstring_to_vec_invalid_characters() {
583 let hexstring = "0x68656c6c6z"; let result = hexstring_to_vec(hexstring);
585 assert!(result.is_err());
586 if let Err(SimulationError::FatalError(msg)) = result {
587 assert!(msg.contains("Invalid hex string"));
588 } else {
589 panic!("Expected EncodingError");
590 }
591 }
592
593 #[test]
594 fn test_json_deserialize_address_list() {
595 let json_input = r#"["0x1234","0xabcd"]"#.as_bytes();
596 let result = json_deserialize_address_list(json_input).unwrap();
597 assert_eq!(result, vec![vec![0x12, 0x34], vec![0xab, 0xcd]]);
598 }
599
600 #[test]
601 fn test_json_deserialize_bigint_list() {
602 let json_input = r#"["0x0b1a2bc2ec500000","0x02c68af0bb140000"]"#.as_bytes();
603 let result = json_deserialize_be_bigint_list(json_input).unwrap();
604 assert_eq!(
605 result,
606 vec![BigInt::from(800000000000000000u64), BigInt::from(200000000000000000u64)]
607 );
608 }
609
610 #[test]
611 fn test_invalid_deserialize_address_list() {
612 let json_input = r#"["invalid_hex"]"#.as_bytes();
613 let result = json_deserialize_address_list(json_input);
614 assert!(result.is_err());
615 }
616
617 #[test]
618 fn test_invalid_deserialize_bigint_list() {
619 let json_input = r#"["invalid_hex"]"#.as_bytes();
620 let result = json_deserialize_be_bigint_list(json_input);
621 assert!(result.is_err());
622 }
623}