alloy_provider/ext/
tenderly.rs

1//! This module extends the Ethereum JSON-RPC provider with the Tenderly namespace's RPC methods.
2use crate::Provider;
3use alloy_eips::BlockNumberOrTag;
4use alloy_network::Network;
5use alloy_primitives::TxHash;
6use alloy_rpc_types_eth::{state::StateOverride, BlockOverrides};
7use alloy_rpc_types_tenderly::TenderlySimulationResult;
8use alloy_transport::TransportResult;
9
10/// Tenderly namespace rpc interface that gives access to several non-standard RPC methods.
11///
12/// Some methods are currently not implemented:
13/// - tenderly_estimateGas
14/// - tenderly_gasPrice
15/// - tenderly_suggestGasFee
16/// - tenderly_estimateGasBundle
17/// - tenderly_decodeInput
18/// - tenderly_decodeError
19/// - tenderly_decodeEvent
20/// - tenderly_functionSignatures
21/// - tenderly_errorSignatures
22/// - tenderly_eventSignatures
23/// - tenderly_getTransactionRange
24/// - tenderly_getContractAbi
25/// - tenderly_getStorageChanges
26#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
27#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
28pub trait TenderlyApi<N: Network>: Send + Sync {
29    /// Simulates a transaction as it would execute on the given block, allowing overrides of state
30    /// variables and balances of all accounts
31    async fn tenderly_simulate_transaction(
32        &self,
33        tx: N::TransactionRequest,
34        block: BlockNumberOrTag,
35        state_overrides: Option<StateOverride>,
36        block_overrides: Option<BlockOverrides>,
37    ) -> TransportResult<TenderlySimulationResult>;
38
39    /// Simulates a transaction as it would execute on the given block, allowing overrides of state
40    /// variables and balances of all accounts
41    async fn tenderly_simulate_bundle(
42        &self,
43        txs: &[N::TransactionRequest],
44        block: BlockNumberOrTag,
45        state_overrides: Option<StateOverride>,
46        block_overrides: Option<BlockOverrides>,
47    ) -> TransportResult<Vec<TenderlySimulationResult>>;
48
49    /// Replays transaction on the blockchain and provides information about the execution.
50    async fn tenderly_trace_transaction(
51        &self,
52        txs: &[TxHash],
53    ) -> TransportResult<TenderlySimulationResult>;
54}
55
56#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
57#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
58impl<N, P> TenderlyApi<N> for P
59where
60    N: Network,
61    P: Provider<N>,
62{
63    async fn tenderly_simulate_transaction(
64        &self,
65        tx: N::TransactionRequest,
66        block: BlockNumberOrTag,
67        state_overrides: Option<StateOverride>,
68        block_overrides: Option<BlockOverrides>,
69    ) -> TransportResult<TenderlySimulationResult> {
70        self.client()
71            .request("tenderly_simulateTransaction", (tx, block, state_overrides, block_overrides))
72            .await
73    }
74
75    async fn tenderly_simulate_bundle(
76        &self,
77        txs: &[N::TransactionRequest],
78        block: BlockNumberOrTag,
79        state_overrides: Option<StateOverride>,
80        block_overrides: Option<BlockOverrides>,
81    ) -> TransportResult<Vec<TenderlySimulationResult>> {
82        self.client()
83            .request("tenderly_simulateBundle", (txs, block, state_overrides, block_overrides))
84            .await
85    }
86
87    async fn tenderly_trace_transaction(
88        &self,
89        txs: &[TxHash],
90    ) -> TransportResult<TenderlySimulationResult> {
91        self.client().request("tenderly_traceTransaction", txs).await
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use std::{env, str::FromStr};
98
99    use alloy_primitives::{address, utils::parse_ether, Address, U256};
100    use alloy_rpc_types_eth::{
101        state::{AccountOverride, StateOverridesBuilder},
102        TransactionRequest,
103    };
104
105    use crate::ProviderBuilder;
106
107    use super::*;
108
109    #[tokio::test]
110    #[ignore]
111    async fn test_tenderly_simulate_transaction_erc20() {
112        let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
113        let provider = ProviderBuilder::new().connect_http(url);
114
115        let gas_price = provider.get_gas_price().await.unwrap();
116        let block = BlockNumberOrTag::Latest;
117        let value = parse_ether("1").unwrap();
118
119        // send to WETH9 to cause an erc20 transfer
120        let tx = TransactionRequest::default()
121            .from(Address::ZERO)
122            .to(address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"))
123            .value(value)
124            .max_fee_per_gas(gas_price + 1)
125            .max_priority_fee_per_gas(gas_price + 1);
126
127        let account_override = AccountOverride::default().with_balance(U256::MAX);
128        let state_override =
129            StateOverridesBuilder::default().append(Address::ZERO, account_override).build();
130
131        let _res = provider
132            .tenderly_simulate_transaction(tx, block, Some(state_override), None)
133            .await
134            .unwrap();
135    }
136
137    #[tokio::test]
138    #[ignore]
139    async fn test_tenderly_simulate_transaction_native() {
140        let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
141        let provider = ProviderBuilder::new().connect_http(url);
142
143        let gas_price = provider.get_gas_price().await.unwrap();
144        let block = BlockNumberOrTag::Latest;
145        let value = parse_ether("1").unwrap();
146
147        let tx = TransactionRequest::default()
148            .from(Address::ZERO)
149            .to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
150            .value(value)
151            .max_fee_per_gas(gas_price + 1)
152            .max_priority_fee_per_gas(gas_price + 1);
153
154        let account_override = AccountOverride::default().with_balance(U256::MAX);
155        let state_override =
156            StateOverridesBuilder::default().append(Address::ZERO, account_override).build();
157
158        let _res = provider
159            .tenderly_simulate_transaction(tx, block, Some(state_override), None)
160            .await
161            .unwrap();
162    }
163
164    #[tokio::test]
165    #[ignore]
166    async fn test_tenderly_simulate_batch() {
167        let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
168        let provider = ProviderBuilder::new().connect_http(url);
169
170        let gas_price = provider.get_gas_price().await.unwrap();
171        let block = BlockNumberOrTag::Latest;
172        let value = parse_ether("1").unwrap();
173
174        let tx = TransactionRequest::default()
175            .from(Address::ZERO)
176            .to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
177            .value(value)
178            .max_fee_per_gas(gas_price + 1)
179            .max_priority_fee_per_gas(gas_price + 1);
180
181        let account_override = AccountOverride::default().with_balance(U256::MAX);
182        let state_override =
183            StateOverridesBuilder::default().append(Address::ZERO, account_override).build();
184
185        let _res = provider
186            .tenderly_simulate_bundle(&[tx.clone(), tx], block, Some(state_override), None)
187            .await
188            .unwrap();
189    }
190
191    #[tokio::test]
192    #[ignore]
193    async fn test_tenderly_trace_transaction() {
194        let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
195        let provider = ProviderBuilder::new().connect_http(url);
196
197        let hash =
198            TxHash::from_str("0x6b2264fa8e28a641d834482d250080b39cbbf39251344573c7504d6137c4b793")
199                .unwrap();
200
201        let _res = provider.tenderly_trace_transaction(&[hash]).await.unwrap();
202    }
203}