Skip to main content

alloy_rpc_types_tenderly/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
4    html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
5)]
6#![cfg_attr(not(test), warn(unused_crate_dependencies))]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9use std::str::FromStr;
10
11use alloy_consensus::TxType;
12use alloy_dyn_abi::{DynSolType, DynSolValue};
13use alloy_eips::BlockNumberOrTag;
14use alloy_primitives::{Address, Bloom, Bytes, FixedBytes, Log, I256, U256};
15use serde::{de::Error, Deserialize, Serialize};
16
17/// Tenderly RPC estimate gas result.
18#[derive(Debug, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct TenderlyEstimateGasResult {
21    /// The estimated gas limit for the transaction.
22    #[serde(with = "alloy_serde::quantity")]
23    pub gas: u64,
24    /// The actual gas used by the transaction.
25    #[serde(with = "alloy_serde::quantity")]
26    pub gas_used: u64,
27}
28
29/// Gas price tier information for Tenderly RPC.
30#[derive(Debug, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct TenderlyGasPriceTier {
33    /// The maximum priority fee per gas.
34    #[serde(with = "alloy_serde::quantity")]
35    pub max_priority_fee_per_gas: u128,
36    /// The maximum fee per gas.
37    #[serde(with = "alloy_serde::quantity")]
38    pub max_fee_per_gas: u128,
39    /// The estimated wait time in milliseconds.
40    pub wait_time: u64,
41}
42
43/// Tenderly RPC gas price result.
44#[derive(Debug, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct TenderlyGasPriceResult {
47    /// The current block number.
48    #[serde(with = "alloy_serde::quantity")]
49    pub current_block_number: u64,
50    /// The base fee per gas.
51    #[serde(with = "alloy_serde::quantity")]
52    pub base_fee_per_gas: u128,
53    /// Gas price tiers for different urgency levels.
54    pub price: TenderlyGasPriceTiers,
55}
56
57/// Gas price tiers for different urgency levels.
58#[derive(Debug, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct TenderlyGasPriceTiers {
61    /// Low urgency tier.
62    pub low: TenderlyGasPriceTier,
63    /// Medium urgency tier.
64    pub medium: TenderlyGasPriceTier,
65    /// High urgency tier.
66    pub high: TenderlyGasPriceTier,
67}
68
69/// Decoded argument for Tenderly decode input.
70#[derive(Clone, Debug, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct TenderlyDecodedArgument {
73    /// Value of the argument.
74    #[serde(rename = "value")]
75    raw_value: serde_json::Value,
76    /// Type of the argument.
77    #[serde(rename = "type")]
78    raw_typ: serde_json::Value,
79    /// Name of the argument.
80    pub name: String,
81    /// True if the argument is indexed (for events).
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub indexed: Option<bool>,
84}
85
86impl TenderlyDecodedArgument {
87    /// Returns the parsed type of the decoded argument.
88    pub fn ty(&self) -> Option<DynSolType> {
89        let raw = self.raw_typ.as_str()?;
90        let Ok(ty) = raw.parse() else {
91            return None;
92        };
93        Some(ty)
94    }
95
96    /// Returns the parsed value of the decoded argument.
97    pub fn value(&self) -> Option<DynSolValue> {
98        let Ok(val) = DecodedValue::parse_dyn_value(&self.raw_value, &self.ty()?) else {
99            return None;
100        };
101        Some(val)
102    }
103}
104
105/// Tenderly RPC decode input result.
106#[derive(Debug, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct TenderlyDecodeInputResult {
109    /// Name of the decoded function or event.
110    pub name: String,
111    /// Confidence level of the decoding (0.0 to 1.0).
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub confidence: Option<f64>,
114    /// Decoded arguments.
115    pub decoded_arguments: Vec<TenderlyDecodedArgument>,
116}
117
118/// Function input type for Tenderly function signatures.
119#[derive(Clone, Debug, Serialize, Deserialize)]
120pub struct TenderlyFunctionInput {
121    /// Type of the input parameter.
122    #[serde(rename = "type")]
123    raw_typ: serde_json::Value,
124}
125
126impl TenderlyFunctionInput {
127    /// Returns the parsed type of the input parameter.
128    pub fn ty(&self) -> Option<DynSolType> {
129        let raw = self.raw_typ.as_str()?;
130        raw.parse().ok()
131    }
132}
133
134/// Function signature for Tenderly function signatures.
135#[derive(Clone, Debug, Serialize, Deserialize)]
136pub struct TenderlyFunctionSignature {
137    /// Name of the function.
138    pub name: String,
139    /// Input parameters of the function.
140    pub inputs: Vec<TenderlyFunctionInput>,
141}
142
143/// Parameters for Tenderly get transaction range request.
144#[derive(Clone, Debug, Serialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct TenderlyTransactionRangeParams {
147    /// The address to check transactions from.
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub from: Option<Address>,
150    /// The address to check transactions to.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub to: Option<Address>,
153    /// The starting block number to search from.
154    pub from_block: BlockNumberOrTag,
155    /// The ending block number to search to.
156    pub to_block: BlockNumberOrTag,
157}
158
159/// Parameters for Tenderly get storage changes request.
160#[derive(Clone, Debug, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct TenderlyStorageQueryParams {
163    /// The address of the contract to fetch storage changes for.
164    pub address: Address,
165    /// The storage slot offset to start querying from (hex string).
166    pub offset: U256,
167}
168
169/// Storage change entry for Tenderly get storage changes response.
170#[derive(Clone, Debug, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct TenderlyStorageChange {
173    /// Block number where the change occurred.
174    #[serde(with = "alloy_serde::quantity")]
175    pub block_number: u64,
176    /// New value of the storage slot.
177    pub value: FixedBytes<32>,
178}
179
180/// Tenderly RPC simulation result.
181#[derive(Debug, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct TenderlySimulationResult {
184    /// The final status of the transaction, typically indicating success or failure.
185    pub status: bool,
186    /// The amount of gas used by the transaction.
187    #[serde(with = "alloy_serde::quantity")]
188    pub gas_used: u64,
189    /// The total amount of gas used when this transaction was executed in the block.
190    #[serde(with = "alloy_serde::quantity")]
191    pub cumulative_gas_used: u64,
192    /// The block the transaction was simulated in.
193    pub block_number: BlockNumberOrTag,
194    /// The type of the transaction.
195    #[serde(rename = "type")]
196    pub typ: TxType,
197    /// The blocks bloom filter.
198    pub logs_bloom: Bloom,
199    /// Logs generated during the execution of the transaction.
200    pub logs: Vec<TenderlyLog>,
201    /// Tenderly trace of the transaction execution.
202    pub trace: Vec<TenderlyTrace>,
203    /// Asset changes caused by the transaction.
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub asset_changes: Option<Vec<AssetChange>>,
206    /// Balance changes caused by the transaction.
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub balance_changes: Option<Vec<BalanceChange>>,
209    /// State changes caused by the transaction.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub state_changes: Option<Vec<StateChange>>,
212}
213
214/// Logs returned by Tenderly RPC, might be decoded.
215#[derive(Clone, Serialize, Deserialize, Debug)]
216#[serde(rename_all = "camelCase")]
217pub struct TenderlyLog {
218    /// Decoded name of the emitted log.
219    pub name: String,
220    /// True if log was emitted by an anonymous event.
221    pub anonymous: bool,
222    /// Decoded inputs of the event.
223    /// This field is not skipped when inputs are `None`.
224    pub inputs: Option<Vec<DecodedValue>>,
225    /// Unencoded logs.
226    pub raw: Log,
227}
228
229/// Log inputs decoded by the tenderly node.
230#[derive(Clone, Debug, Serialize, Deserialize)]
231pub struct DecodedValue {
232    /// Value of the input.
233    #[serde(rename = "value")]
234    raw_value: serde_json::Value,
235    /// Type of the input.
236    #[serde(rename = "type")]
237    raw_typ: serde_json::Value,
238    /// Name of the input.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub name: Option<String>,
241    /// True if the input is indexed.
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub indexed: Option<bool>,
244}
245
246impl DecodedValue {
247    /// Returns the parsed type of the log input.
248    pub fn ty(&self) -> Option<DynSolType> {
249        let raw = self.raw_typ.as_str()?;
250        let Ok(ty) = raw.parse() else {
251            return None;
252        };
253        Some(ty)
254    }
255
256    /// Returns the parsed type of the log input.
257    #[deprecated = "Use `Self::ty` instead"]
258    pub fn typ(&self) -> Option<DynSolType> {
259        self.ty()
260    }
261
262    /// Returns the parsed value of the log input.
263    pub fn value(&self) -> Option<DynSolValue> {
264        let Ok(val) = Self::parse_dyn_value(&self.raw_value, &self.ty()?) else {
265            return None;
266        };
267        Some(val)
268    }
269
270    /// Parses a JSON value into a `DynSolValue` based on the given `DynSolType`.
271    pub(crate) fn parse_dyn_value(
272        val: &serde_json::Value,
273        ty: &DynSolType,
274    ) -> Result<DynSolValue, serde_json::error::Error> {
275        use serde_json::Error;
276
277        match ty {
278            DynSolType::Bool => {
279                val.as_bool().map(DynSolValue::Bool).ok_or_else(|| Error::custom("expected bool"))
280            }
281            DynSolType::Uint(bits) => val
282                .as_str()
283                .ok_or_else(|| Error::custom("expected string"))
284                .and_then(|a| U256::from_str(a).map_err(Error::custom))
285                .map(|u| DynSolValue::Uint(u, *bits)),
286            DynSolType::Int(bits) => val
287                .as_str()
288                .ok_or_else(|| Error::custom("expected string"))
289                .and_then(|a| I256::from_str(a).map_err(Error::custom))
290                .map(|i| DynSolValue::Int(i, *bits)),
291            DynSolType::Address => val
292                .as_str()
293                .ok_or_else(|| Error::custom("expected string"))
294                .and_then(|a| Address::from_str(a).map_err(Error::custom))
295                .map(DynSolValue::Address),
296            DynSolType::FixedBytes(size) => val
297                .as_str()
298                .ok_or_else(|| Error::custom("expected string"))
299                .and_then(|a| FixedBytes::from_str(a).map_err(Error::custom))
300                .map(|b| DynSolValue::FixedBytes(b, *size)),
301            DynSolType::Bytes => val
302                .as_str()
303                .ok_or_else(|| Error::custom("expected string"))
304                .and_then(|b| Bytes::from_str(b).map_err(Error::custom))
305                .map(|b| DynSolValue::Bytes(b.into())),
306            DynSolType::String => val
307                .as_str()
308                .ok_or_else(|| Error::custom("expected string"))
309                .map(|s| DynSolValue::String(s.to_owned())),
310            DynSolType::Array(inner) => {
311                let arr = val.as_array().ok_or_else(|| Error::custom("expected array"))?;
312                let values: Vec<DynSolValue> = arr
313                    .iter()
314                    .map(|v| Self::parse_dyn_value(v, inner))
315                    .collect::<Result<Vec<_>, _>>()?;
316                Ok(DynSolValue::Array(values))
317            }
318            DynSolType::FixedArray(inner, size) => {
319                let arr = val.as_array().ok_or_else(|| Error::custom("expected array"))?;
320                if arr.len() != *size {
321                    return Err(Error::custom("array size mismatch"));
322                }
323                let values: Vec<DynSolValue> = arr
324                    .iter()
325                    .map(|v| Self::parse_dyn_value(v, inner))
326                    .collect::<Result<Vec<_>, _>>()?;
327                Ok(DynSolValue::FixedArray(values))
328            }
329            DynSolType::Tuple(types) => {
330                let arr = val.as_array().ok_or_else(|| Error::custom("expected tuple"))?;
331                if arr.len() != types.len() {
332                    return Err(Error::custom("tuple length mismatch"));
333                }
334                let values = arr
335                    .iter()
336                    .zip(types.iter())
337                    .map(|(v, t)| Self::parse_dyn_value(v, t))
338                    .collect::<Result<Vec<_>, _>>()?;
339                Ok(DynSolValue::Tuple(values))
340            }
341            _ => Err(Error::custom("type is not supported")),
342        }
343    }
344}
345
346/// Call trace generated by tenderly.
347#[derive(Clone, Serialize, Deserialize, Debug)]
348#[serde(rename_all = "camelCase")]
349pub struct TenderlyTrace {
350    /// Call type.
351    pub r#type: TenderlyCallType,
352    /// Origin address of the call.
353    pub from: Address,
354    /// Target address of the call.
355    pub to: Address,
356    /// Gas used by the call.
357    #[serde(with = "alloy_serde::quantity")]
358    pub gas: u64,
359    /// Gas used by the call.
360    #[serde(with = "alloy_serde::quantity")]
361    pub gas_used: u64,
362    /// Value of the call.
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub value: Option<U256>,
365    /// Error caused by the call.
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub error: Option<String>,
368    /// Input of the call.
369    pub input: Bytes,
370    /// Decoded Trace Input
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub decoded_input: Option<Vec<DecodedValue>>,
373    /// Name of the method.
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub method: Option<String>,
376    /// Output of the call.
377    pub output: Bytes,
378    /// Decoded output of the call.
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub decoded_output: Option<Vec<DecodedValue>>,
381    /// How many subtraces this trace has.
382    pub subtraces: usize,
383    /// The identifier of this transaction trace in the set.
384    ///
385    /// This gives the exact location in the call trace
386    /// [index in root CALL, index in first CALL, index in second CALL, …].
387    pub trace_address: Vec<usize>,
388}
389
390/// Types of EVM calls.
391#[derive(Clone, Serialize, Deserialize, Debug)]
392#[serde(rename_all = "UPPERCASE")]
393pub enum TenderlyCallType {
394    /// Call type.
395    Call,
396    /// Deprecated CallCode type.
397    CallCode,
398    /// StaticCall type.
399    StaticCall,
400    /// DelegateCall type.
401    DelegateCall,
402    /// AuthorizedCall type.
403    AuthCall,
404}
405
406/// Information about the assets affected by the transaction.
407#[derive(Clone, Serialize, Deserialize, Debug)]
408#[serde(rename_all = "camelCase")]
409pub struct AssetChange {
410    /// Information about the exchanged asset.
411    pub asset_info: AssetInfo,
412    /// Type of the asset change.
413    pub r#type: ChangeType,
414    /// Sender address.
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub from: Option<Address>,
417    /// Recipient address.
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub to: Option<Address>,
420    /// Unformatted amount of the asset.
421    pub raw_amount: U256,
422    /// Amount formatted according to asset decimals.
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub amount: Option<String>,
425    /// Dollar value of the change.
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub dollar_value: Option<String>,
428}
429
430/// Information describing an onchain asset.
431#[derive(Clone, Serialize, Deserialize, Debug)]
432#[serde(rename_all = "camelCase")]
433pub struct AssetInfo {
434    /// Token standard of the asset.
435    pub standard: AssetStandard,
436    /// Fungibility of the asset, omitted if unknown.
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub r#type: Option<AssetFungibility>,
439    /// Address of the token contract.
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub contract_address: Option<Address>,
442    /// Symbol of the asset.
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub symbol: Option<String>,
445    /// Name of the asset.
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub name: Option<String>,
448    /// URL of the asset logo.
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub logo: Option<String>,
451    /// Decimals of the asset.
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub decimals: Option<u8>,
454    /// Dollar value of the asset.
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub dollar_value: Option<String>,
457}
458
459/// Token standard of an asset.
460#[derive(Clone, Serialize, Deserialize, Debug)]
461#[serde(rename_all = "UPPERCASE")]
462pub enum AssetStandard {
463    /// Native currency of the network.
464    #[serde(rename = "NativeCurrency")]
465    NativeCurrency,
466    /// Fungible token.
467    Erc20,
468    /// Non-fungible token.
469    Erc721,
470    /// Multi-token.
471    Erc1155,
472}
473
474/// Token standard of an asset.
475#[derive(Clone, Serialize, Deserialize, Debug)]
476#[serde(rename_all = "PascalCase")]
477pub enum AssetFungibility {
478    /// Native asset.
479    Native,
480    /// Fungible asset.
481    Fungible,
482    /// Non fungible asset.
483    NonFungible,
484}
485
486/// Type of asset change.
487#[derive(Clone, Serialize, Deserialize, Debug)]
488#[serde(rename_all = "PascalCase")]
489pub enum ChangeType {
490    /// Asset mint.
491    Mint,
492    /// Asset burn.
493    Burn,
494    /// Asset transfer.
495    Transfer,
496}
497
498/// Balance change of an address caused by a transaction.
499#[derive(Clone, Serialize, Deserialize, Debug)]
500#[serde(rename_all = "camelCase")]
501pub struct BalanceChange {
502    /// Address affected by the transaction.
503    pub address: Address,
504    /// Dollar value of the
505    pub dollar_value: String,
506    /// Identifiers of the traces affecting this balance change.
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub transfers: Option<Vec<usize>>,
509}
510
511/// State changes of an address caused by a transaction
512#[derive(Clone, Serialize, Deserialize, Debug)]
513#[serde(rename_all = "camelCase")]
514pub struct StateChange {
515    /// Address affected by the transaction..
516    pub address: Address,
517    /// Nonce change caused by the transaction.
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub nonce: Option<ValueChange>,
520    /// Balance change caused by the transaction.
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub balance: Option<ValueChange>,
523    /// Storage change caused by the transaction.
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub storage: Option<Vec<StorageSlotChange>>,
526}
527
528/// Describes the change of a storage slot due to a transaction.
529#[derive(Clone, Serialize, Deserialize, Debug)]
530#[serde(rename_all = "camelCase")]
531pub struct StorageSlotChange {
532    /// Storage slot.
533    pub slot: FixedBytes<32>,
534    /// Value before the transaction.
535    pub previous_value: FixedBytes<32>,
536    /// Value after the transaction.
537    pub new_value: FixedBytes<32>,
538}
539
540/// Describes the change of a value due to a transaction.
541#[derive(Clone, Serialize, Deserialize, Debug)]
542#[serde(rename_all = "camelCase")]
543pub struct ValueChange {
544    /// Value before the transaction.
545    pub previous_value: U256,
546    /// Value after the transaction.
547    pub new_value: U256,
548}
549
550#[cfg(test)]
551mod tests {
552    use alloy_dyn_abi::{DynSolType, DynSolValue};
553
554    use crate::{
555        TenderlyDecodeInputResult, TenderlyEstimateGasResult, TenderlyGasPriceResult,
556        TenderlySimulationResult,
557    };
558
559    #[test]
560    fn test_success_response() {
561        let input = include_str!("../test_data/success.json");
562        let parsed: TenderlySimulationResult = serde_json::from_str(input).unwrap();
563
564        // strip whitespace to force equal formatting
565        assert_eq!(
566            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
567            input.split_whitespace().collect::<String>()
568        );
569    }
570
571    #[test]
572    fn test_failure_response() {
573        let input = include_str!("../test_data/failure.json");
574        let parsed: TenderlySimulationResult = serde_json::from_str(input).unwrap();
575
576        assert_eq!(
577            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
578            input.split_whitespace().collect::<String>()
579        );
580    }
581
582    #[test]
583    fn test_bundle_success_response() {
584        let input = include_str!("../test_data/bundle_success.json");
585        let parsed: Vec<TenderlySimulationResult> = serde_json::from_str(input).unwrap();
586
587        assert_eq!(
588            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
589            input.split_whitespace().collect::<String>()
590        );
591    }
592
593    #[test]
594    fn test_trace_success_response() {
595        let input = include_str!("../test_data/trace_success.json");
596        let parsed: TenderlySimulationResult = serde_json::from_str(input).unwrap();
597
598        assert_eq!(
599            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
600            input.split_whitespace().collect::<String>()
601        );
602    }
603
604    #[test]
605    fn test_trace_complex_response() {
606        let input = include_str!("../test_data/trace_complex.json");
607        let parsed: TenderlySimulationResult = serde_json::from_str(input).unwrap();
608
609        assert_eq!(
610            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
611            input.split_whitespace().collect::<String>()
612        );
613    }
614
615    #[test]
616    fn test_trace_swap_response() {
617        let input = include_str!("../test_data/trace_swap.json");
618        let parsed: TenderlySimulationResult = serde_json::from_str(input).unwrap();
619
620        assert_eq!(
621            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
622            input.split_whitespace().collect::<String>()
623        );
624    }
625
626    #[test]
627    fn test_estimate_gas_response() {
628        let input = include_str!("../test_data/estimate_gas.json");
629        let parsed: TenderlyEstimateGasResult = serde_json::from_str(input).unwrap();
630
631        assert_eq!(parsed.gas, 0x12579);
632        assert_eq!(parsed.gas_used, 0xff06);
633
634        assert_eq!(
635            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
636            input.split_whitespace().collect::<String>()
637        );
638    }
639
640    #[test]
641    fn test_gas_price_response() {
642        let input = include_str!("../test_data/gas_price.json");
643        let parsed: TenderlyGasPriceResult = serde_json::from_str(input).unwrap();
644
645        assert_eq!(parsed.current_block_number, 0x14a0bdb);
646        assert_eq!(parsed.base_fee_per_gas, 0xbcdd3e0f);
647        assert_eq!(parsed.price.low.max_priority_fee_per_gas, 0x27f840a);
648        assert_eq!(parsed.price.low.max_fee_per_gas, 0x1097c5d8b);
649        assert_eq!(parsed.price.low.wait_time, 36000);
650        assert_eq!(parsed.price.medium.max_priority_fee_per_gas, 0x9b4c5bb);
651        assert_eq!(parsed.price.medium.max_fee_per_gas, 0x1137c6003);
652        assert_eq!(parsed.price.medium.wait_time, 24000);
653        assert_eq!(parsed.price.high.max_priority_fee_per_gas, 0x10128f8e);
654        assert_eq!(parsed.price.high.max_fee_per_gas, 0x11c5174a8);
655        assert_eq!(parsed.price.high.wait_time, 12000);
656
657        assert_eq!(
658            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
659            input.split_whitespace().collect::<String>()
660        );
661    }
662
663    #[test]
664    fn test_estimate_gas_bundle_response() {
665        let input = include_str!("../test_data/estimate_gas_bundle.json");
666        let parsed: Vec<TenderlyEstimateGasResult> = serde_json::from_str(input).unwrap();
667
668        assert_eq!(parsed.len(), 4);
669        assert_eq!(parsed[0].gas, 0x12579);
670        assert_eq!(parsed[0].gas_used, 0xff06);
671        assert_eq!(parsed[1].gas, 0x10918);
672        assert_eq!(parsed[1].gas_used, 0xb551);
673        assert_eq!(parsed[2].gas, 0xa621);
674        assert_eq!(parsed[2].gas_used, 0x6625);
675        assert_eq!(parsed[3].gas, 0x10649);
676        assert_eq!(parsed[3].gas_used, 0xb249);
677
678        assert_eq!(
679            serde_json::to_string(&parsed).unwrap().split_whitespace().collect::<String>(),
680            input.split_whitespace().collect::<String>()
681        );
682    }
683
684    #[test]
685    fn test_decode_input_response() {
686        let input = include_str!("../test_data/decode_input.json");
687        let parsed: TenderlyDecodeInputResult = serde_json::from_str(input).unwrap();
688
689        assert_eq!(parsed.name, "transfer");
690        assert_eq!(parsed.decoded_arguments.len(), 2);
691
692        // Test first argument (address)
693        let arg0 = &parsed.decoded_arguments[0];
694        assert_eq!(arg0.name, "arg0");
695        let ty0 = arg0.ty().expect("should parse address type");
696        assert!(matches!(ty0, DynSolType::Address));
697        let value0 = arg0.value().expect("should parse address value");
698        assert!(matches!(value0, DynSolValue::Address(_)));
699
700        // Test second argument (uint256)
701        let arg1 = &parsed.decoded_arguments[1];
702        assert_eq!(arg1.name, "arg1");
703        let ty1 = arg1.ty().expect("should parse uint256 type");
704        assert!(matches!(ty1, DynSolType::Uint(_)));
705        let value1 = arg1.value().expect("should parse uint256 value");
706        assert!(matches!(value1, DynSolValue::Uint(_, _)));
707
708        // Round-trip test: deserialize and serialize back to verify structure
709        let serialized = serde_json::to_string(&parsed).unwrap();
710        let reparsed: TenderlyDecodeInputResult = serde_json::from_str(&serialized).unwrap();
711        assert_eq!(reparsed.name, parsed.name);
712        assert_eq!(reparsed.decoded_arguments.len(), parsed.decoded_arguments.len());
713    }
714
715    #[test]
716    fn test_decode_error_response() {
717        let input = include_str!("../test_data/decode_error.json");
718        let parsed: TenderlyDecodeInputResult = serde_json::from_str(input).unwrap();
719
720        assert_eq!(parsed.name, "ERC20InsufficientBalance");
721        assert_eq!(parsed.decoded_arguments.len(), 3);
722
723        // Test first argument (address)
724        let arg0 = &parsed.decoded_arguments[0];
725        assert_eq!(arg0.name, "arg0");
726        let ty0 = arg0.ty().expect("should parse address type");
727        assert!(matches!(ty0, DynSolType::Address));
728        let value0 = arg0.value().expect("should parse address value");
729        assert!(matches!(value0, DynSolValue::Address(_)));
730
731        // Test second argument (uint256)
732        let arg1 = &parsed.decoded_arguments[1];
733        assert_eq!(arg1.name, "arg1");
734        let ty1 = arg1.ty().expect("should parse uint256 type");
735        assert!(matches!(ty1, DynSolType::Uint(_)));
736        let value1 = arg1.value().expect("should parse uint256 value");
737        assert!(matches!(value1, DynSolValue::Uint(_, _)));
738
739        // Test third argument (uint256)
740        let arg2 = &parsed.decoded_arguments[2];
741        assert_eq!(arg2.name, "arg2");
742        let ty2 = arg2.ty().expect("should parse uint256 type");
743        assert!(matches!(ty2, DynSolType::Uint(_)));
744        let value2 = arg2.value().expect("should parse uint256 value");
745        assert!(matches!(value2, DynSolValue::Uint(_, _)));
746
747        // Round-trip test: deserialize and serialize back to verify structure
748        let serialized = serde_json::to_string(&parsed).unwrap();
749        let reparsed: TenderlyDecodeInputResult = serde_json::from_str(&serialized).unwrap();
750        assert_eq!(reparsed.name, parsed.name);
751        assert_eq!(reparsed.decoded_arguments.len(), parsed.decoded_arguments.len());
752    }
753}