tycho_simulation/evm/protocol/vm/
utils.rs

1use 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        // Check for revert situation (if error message starts with "0x")
24        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            // Check if we are running out of gas
34            if let (Some(gas_limit), Some(gas_used)) = (gas_limit, gas_used) {
35                // if we used up 97% or more issue a OutOfGas error.
36                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        // Check if "OutOfGas" is part of the error message
54        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()), /* Otherwise return the
78                                                                    * original error */
79    }
80}
81
82fn parse_solidity_error_message(data: &str) -> String {
83    // 10 for "0x" + 8 hex chars error signature
84    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        // Check for specific error selectors:
91        // Solidity Error(string) signature: 0x08c379a0
92        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            // Solidity Panic(uint256) signature: 0x4e487b71
98        } 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        // Try decoding as a string (old Solidity revert case)
109        if let Ok(decoded) = String::abi_decode(&data_bytes) {
110            return decoded;
111        }
112
113        // Custom error, try to decode string again with offset
114        if let Ok(decoded) = String::abi_decode(&data_bytes[4..]) {
115            return decoded;
116        }
117    }
118    // Fallback if no decoding succeeded
119    format!("Failed to decode: {data}")
120}
121
122/// Get storage slot index of a value stored at a certain key in a mapping
123///
124/// # Arguments
125///
126/// * `key`: Key in a mapping. Can be any H160 value (such as an address).
127/// * `mapping_slot`: An `U256` representing the storage slot at which the mapping itself is stored.
128///   See the examples for more explanation.
129/// * `compiler`: The compiler with which the target contract was compiled. Solidity and Vyper
130///   handle maps differently.
131///
132/// # Returns
133///
134/// An `U256` representing the  index of a storage slot where the value at the given
135/// key is stored.
136///
137/// # Examples
138///
139/// If a mapping is declared as a first variable in Solidity code, its storage slot
140/// is 0 (e.g. `balances` in our mocked ERC20 contract). Here's how to compute
141/// a storage slot where balance of a given account is stored:
142///
143/// ```
144/// use alloy::primitives::{U256, Address};
145/// use tycho_simulation::evm::ContractCompiler;
146/// use tycho_simulation::evm::protocol::vm::utils::get_storage_slot_index_at_key;
147/// let address: Address = "0xC63135E4bF73F637AF616DFd64cf701866BB2628".parse().expect("Invalid address");
148/// get_storage_slot_index_at_key(address, U256::from(0), ContractCompiler::Solidity);
149/// ```
150///
151/// For nested mappings, we need to apply the function twice. An example of this is
152/// `allowances` in ERC20. It is a mapping of form:
153/// `HashMap<Owner, HashMap<Spender, U256>>`. In our mocked ERC20 contract, `allowances`
154/// is a second variable, so it is stored at slot 1. Here's how to get a storage slot
155/// where an allowance of `address_spender` to spend `address_owner`'s money is stored:
156///
157/// ```
158/// use alloy::primitives::{U256, Address};
159/// use tycho_simulation::evm::ContractCompiler;
160/// use tycho_simulation::evm::protocol::vm::utils::get_storage_slot_index_at_key;
161/// let address_spender: Address = "0xC63135E4bF73F637AF616DFd64cf701866BB2628".parse().expect("Invalid address");
162/// let address_owner: Address = "0x6F4Feb566b0f29e2edC231aDF88Fe7e1169D7c05".parse().expect("Invalid address");
163/// get_storage_slot_index_at_key(address_spender, get_storage_slot_index_at_key(address_owner, U256::from(1), ContractCompiler::Solidity), ContractCompiler::Solidity);
164/// ```
165///
166/// # See Also
167///
168/// [Solidity Storage Layout documentation](https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html#mappings-and-dynamic-arrays)
169pub 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); // Prepend zeros to the start
178    }
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
199/// Fetches the bytecode for a specified contract address, returning an error if the address is
200/// an Externally Owned Account (EOA) or if no code is associated with it.
201///
202/// This function checks the specified address on the blockchain, attempting to retrieve any
203/// contract bytecode deployed at that address. If the address corresponds to an EOA or any
204/// other address without associated bytecode, an `RpcError::EmptyResponse` error is returned.
205///
206/// # Parameters
207/// - `address`: The address of the account or contract to query, as a string.
208/// - `connection_string`: An optional RPC connection string. If not provided, the function will
209///   default to the `RPC_URL` environment variable.
210///
211/// # Returns
212/// - `Ok(Bytecode)`: The bytecode of the contract at the specified address, if present.
213/// - `Err(RpcError)`: An error if the address does not have associated bytecode, if there is an
214///   issue with the RPC connection, or if the address is invalid.
215///
216/// # Errors
217/// - Returns `RpcError::InvalidRequest` if `address` is not parsable or if no RPC URL is set.
218/// - Returns `RpcError::EmptyResponse` if the address has no associated bytecode (e.g., EOA).
219/// - Returns `RpcError::InvalidResponse` for issues with the RPC provider response.
220pub(crate) async fn get_code_for_contract(
221    address: &str,
222    connection_string: Option<String>,
223) -> Result<Bytecode, SimulationError> {
224    // Get the connection string, defaulting to the RPC_URL environment variable
225    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    // Call eth_getCode to get the bytecode of the contract
239    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            // Create a provider with the URL
265            let provider = ProviderBuilder::new()
266                .connect(connection_string)
267                .await?;
268            provider.get_code_at(addr).await
269        })
270    })
271}
272
273/// Converts a hexadecimal string into a fixed-size 32-byte array.
274///
275/// This function takes a string slice (e.g., a pool ID) that may or may not have
276/// a `0x` prefix. It decodes the hex string into bytes, ensuring it does not exceed
277/// 32 bytes in length. If the string is valid and fits within 32 bytes, the bytes
278/// are copied into a `[u8; 32]` array, with right zero-padding for unused bytes.
279///
280/// # Arguments
281///
282/// * `pool_id` - A string slice representing a hexadecimal pool ID. It can optionally start with
283///   the `0x` prefix.
284///
285/// # Returns
286///
287/// * `Ok([u8; 32])` - On success, returns a 32-byte array with the decoded bytes. If the input is
288///   shorter than 32 bytes, the rest of the array is right padded with zeros.
289/// * `Err(SimulationError)` - Returns an error if:
290///     - The input string is not a valid hexadecimal string.
291///     - The decoded bytes exceed 32 bytes in length.
292///
293/// # Example
294/// ```
295/// use tycho_simulation::evm::protocol::vm::utils::string_to_bytes32;
296///
297/// let pool_id = "0x1234abcd";
298/// match string_to_bytes32(pool_id) {
299///     Ok(bytes32) => println!("Bytes32: {:?}", bytes32),
300///     Err(e) => eprintln!("Error: {}", e),
301/// }
302pub 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
318/// Decodes a JSON-encoded list of hexadecimal strings into a `Vec<Vec<u8>>`.
319///
320/// This function parses a JSON array where each element is a string representing a hexadecimal
321/// value. It converts each hex string into a vector of bytes (`Vec<u8>`), and aggregates them into
322/// a `Vec<Vec<u8>>`.
323///
324/// # Arguments
325///
326/// * `input` - A byte slice (`&[u8]`) containing JSON-encoded data. The JSON must be a valid array
327///   of hex strings.
328///
329/// # Returns
330///
331/// * `Ok(Vec<Vec<u8>>)` - On success, returns a vector of byte vectors.
332/// * `Err(SimulationError)` - Returns an error if:
333///     - The input is not valid JSON.
334///     - The JSON is not an array.
335///     - Any array element is not a string.
336///     - Any string is not a valid hexadecimal string.
337///
338/// # Example
339/// ```
340/// use tycho_simulation::evm::protocol::vm::utils::json_deserialize_address_list;
341///
342/// let json_input = br#"["0x1234", "0xc0ffee"]"#;
343/// match json_deserialize_address_list(json_input) {
344///     Ok(result) => println!("Decoded: {:?}", result),
345///     Err(e) => eprintln!("Error: {}", e),
346/// }
347/// ```
348pub 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
372/// Decodes a JSON-encoded list of hexadecimal strings into a `Vec<BigInt>`.
373///
374/// This function parses a JSON array where each element is a string representing a hexadecimal
375/// value. It converts each hex string into a `BigInt` using big-endian byte interpretation, and
376/// aggregates them into a `Vec<BigInt>`.
377///
378/// # Arguments
379///
380/// * `input` - A byte slice (`&[u8]`) containing JSON-encoded data. The JSON must be a valid array
381///   of hex strings.
382///
383/// # Returns
384///
385/// * `Ok(Vec<BigInt>)` - On success, returns a vector of `BigInt` values.
386/// * `Err(SimulationError)` - Returns an error if:
387///     - The input is not valid JSON.
388///     - The JSON is not an array.
389///     - Any array element is not a string.
390///     - Any string is not a valid hexadecimal string.
391///
392/// # Example
393/// ```
394/// use tycho_simulation::evm::protocol::vm::utils::json_deserialize_be_bigint_list;
395/// use num_bigint::BigInt;
396/// use tycho_simulation::evm;
397/// let json_input = br#"["0x1234", "0xdeadbeef"]"#;
398/// match json_deserialize_be_bigint_list(json_input) {
399///     Ok(result) => println!("Decoded BigInts: {:?}", result),
400///     Err(e) => eprintln!("Error: {}", e),
401/// }
402/// ```
403pub 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        // Test out-of-gas situation with gas limit and gas used provided
471        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        // Test out-of-gas situation without gas limit info
489        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        // Test for non-revert, non-out-of-gas, non-storage errors
521        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        // Test parsing Solidity Error(string) message
538        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        // Test parsing Solidity Panic(uint256) message
548        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        // Test failed decoding with invalid data
558        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"; // Invalid character 'z'
584        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}