alloy_provider/ext/
tenderly.rs1use 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#[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 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 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 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 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}