1use alloy_network::Network;
2use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256};
3use alloy_transport::TransportResult;
4
5use crate::Provider;
6
7#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
10#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
11pub trait TenderlyAdminApi<N: Network>: Send + Sync {
12 async fn tenderly_set_next_block_timestamp(&self, timestamp: u64) -> TransportResult<u64>;
15
16 async fn tenderly_set_balance(
19 &self,
20 wallet: Address,
21 balance: U256,
22 ) -> TransportResult<FixedBytes<32>>;
23
24 async fn tenderly_set_balance_batch(
27 &self,
28 wallets: &[Address],
29 balance: U256,
30 ) -> TransportResult<FixedBytes<32>>;
31
32 async fn tenderly_add_balance(
35 &self,
36 wallet: Address,
37 amount: U256,
38 ) -> TransportResult<FixedBytes<32>>;
39
40 async fn tenderly_add_balance_batch(
43 &self,
44 wallets: &[Address],
45 amount: U256,
46 ) -> TransportResult<FixedBytes<32>>;
47
48 async fn tenderly_set_erc20_balance(
51 &self,
52 token: Address,
53 wallet: Address,
54 balance: U256,
55 ) -> TransportResult<FixedBytes<32>>;
56
57 async fn tenderly_set_storage_at(
60 &self,
61 address: Address,
62 slot: U256,
63 value: B256,
64 ) -> TransportResult<FixedBytes<32>>;
65
66 async fn tenderly_set_code(
69 &self,
70 address: Address,
71 code: Bytes,
72 ) -> TransportResult<FixedBytes<32>>;
73}
74
75#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
76#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
77impl<N, P> TenderlyAdminApi<N> for P
78where
79 N: Network,
80 P: Provider<N>,
81{
82 async fn tenderly_set_next_block_timestamp(&self, timestamp: u64) -> TransportResult<u64> {
83 self.client().request("tenderly_setNextBlockTimestamp", timestamp).await
84 }
85
86 async fn tenderly_set_balance(
87 &self,
88 wallet: Address,
89 balance: U256,
90 ) -> TransportResult<FixedBytes<32>> {
91 self.client().request("tenderly_setBalance", (wallet, balance)).await
92 }
93
94 async fn tenderly_set_balance_batch(
95 &self,
96 wallets: &[Address],
97 balance: U256,
98 ) -> TransportResult<FixedBytes<32>> {
99 self.client().request("tenderly_setBalance", (wallets, balance)).await
100 }
101
102 async fn tenderly_add_balance(
103 &self,
104 wallet: Address,
105 balance: U256,
106 ) -> TransportResult<FixedBytes<32>> {
107 self.client().request("tenderly_addBalance", (wallet, balance)).await
108 }
109
110 async fn tenderly_add_balance_batch(
111 &self,
112 wallets: &[Address],
113 balance: U256,
114 ) -> TransportResult<FixedBytes<32>> {
115 self.client().request("tenderly_addBalance", (wallets, balance)).await
116 }
117
118 async fn tenderly_set_erc20_balance(
119 &self,
120 token: Address,
121 wallet: Address,
122 balance: U256,
123 ) -> TransportResult<FixedBytes<32>> {
124 self.client().request("tenderly_setErc20Balance", (token, wallet, balance)).await
125 }
126
127 async fn tenderly_set_storage_at(
128 &self,
129 address: Address,
130 slot: U256,
131 value: B256,
132 ) -> TransportResult<FixedBytes<32>> {
133 self.client()
134 .request("tenderly_setStorageAt", (address, FixedBytes::from(slot), value))
135 .await
136 }
137
138 async fn tenderly_set_code(
139 &self,
140 address: Address,
141 code: Bytes,
142 ) -> TransportResult<FixedBytes<32>> {
143 self.client().request("tenderly_setCode", (address, code)).await
144 }
145}
146
147#[cfg(test)]
148mod test {
149 use std::env;
150
151 use alloy_network::TransactionBuilder;
152 use alloy_primitives::{address, bytes, Address, U256};
153 use alloy_rpc_types_eth::TransactionRequest;
154 use alloy_sol_types::{sol, SolCall};
155
156 use crate::ProviderBuilder;
157
158 use super::*;
159
160 #[tokio::test]
161 #[ignore]
162 async fn test_tenderly_set_balance() {
163 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
164 let provider = ProviderBuilder::new().connect_http(url);
165
166 let alice = Address::random();
167 provider.tenderly_set_balance(alice, U256::ONE).await.unwrap();
168
169 let balance = provider.get_balance(alice).await.unwrap();
170 assert_eq!(balance, U256::ONE);
171 }
172
173 #[tokio::test]
174 #[ignore]
175 async fn test_tenderly_set_balance_batch() {
176 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
177 let provider = ProviderBuilder::new().connect_http(url);
178
179 let alice = Address::random();
180 let bob = Address::random();
181 let wallets = vec![alice, bob];
182
183 provider.tenderly_set_balance_batch(&wallets, U256::ONE).await.unwrap();
184
185 let balance = provider.get_balance(alice).await.unwrap();
186 assert_eq!(balance, U256::ONE);
187
188 let balance = provider.get_balance(bob).await.unwrap();
189 assert_eq!(balance, U256::ONE);
190 }
191
192 #[tokio::test]
193 #[ignore]
194 async fn test_tenderly_add_balance() {
195 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
196 let provider = ProviderBuilder::new().connect_http(url);
197
198 let alice = Address::random();
199 provider.tenderly_add_balance(alice, U256::ONE).await.unwrap();
200 provider.tenderly_add_balance(alice, U256::ONE).await.unwrap();
201
202 let balance = provider.get_balance(alice).await.unwrap();
203 assert_eq!(balance, U256::from(2));
204 }
205
206 #[tokio::test]
207 #[ignore]
208 async fn test_tenderly_add_balance_batch() {
209 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
210 let provider = ProviderBuilder::new().connect_http(url);
211
212 let alice = Address::random();
213 let bob = Address::random();
214 let wallets = vec![alice, bob];
215
216 provider.tenderly_add_balance_batch(&wallets, U256::ONE).await.unwrap();
217 provider.tenderly_add_balance_batch(&wallets, U256::ONE).await.unwrap();
218
219 let balance = provider.get_balance(alice).await.unwrap();
220 assert_eq!(balance, U256::from(2));
221
222 let balance = provider.get_balance(bob).await.unwrap();
223 assert_eq!(balance, U256::from(2));
224 }
225
226 #[tokio::test]
227 #[ignore]
228 async fn test_tenderly_set_erc20_balance() {
229 sol! {
230 contract IERC20 {
231 function balanceOf(address target) external view returns (uint256);
232 }
233 }
234
235 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
236 let provider = ProviderBuilder::new().connect_http(url);
237
238 let alice = Address::random();
239 let dai = address!("0x6B175474E89094C44Da98b954EedeAC495271d0F");
240
241 let input = IERC20::balanceOfCall::new((alice,)).abi_encode();
242 let balance_of_tx = TransactionRequest::default().with_to(dai).with_input(input);
243
244 provider.tenderly_set_erc20_balance(dai, alice, U256::ONE).await.unwrap();
245 let balance = provider.call(balance_of_tx).await.unwrap();
246 assert_eq!(IERC20::balanceOfCall::abi_decode_returns(&balance).unwrap(), U256::ONE);
247 }
248
249 #[tokio::test]
250 #[ignore]
251 async fn test_tenderly_set_storage() {
252 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
253 let provider = ProviderBuilder::new().connect_http(url);
254
255 let alice = Address::random();
256 let key = U256::from(42);
257
258 let before = provider.get_storage_at(alice, key).await.unwrap();
259 assert_eq!(before, U256::ZERO);
260
261 provider.tenderly_set_storage_at(alice, key, U256::from(7).into()).await.unwrap();
262
263 let after = provider.get_storage_at(alice, key).await.unwrap();
264 assert_eq!(after, U256::from(7));
265 }
266
267 #[tokio::test]
268 #[ignore]
269 async fn test_tenderly_set_code() {
270 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
271 let provider = ProviderBuilder::new().connect_http(url);
272
273 let alice = Address::random();
274 let code = bytes!("0xdeadbeef");
275
276 provider.tenderly_set_code(alice, code.clone()).await.unwrap();
277
278 let after = provider.get_code_at(alice).await.unwrap();
279 assert_eq!(after, code);
280 }
281}