alloy_provider/ext/
tenderly_admin.rs

1use alloy_network::Network;
2use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256};
3use alloy_transport::TransportResult;
4
5use crate::Provider;
6
7/// Tenderly namespace rpc interface that gives access to several admin
8/// RPC methods on tenderly virtual testnets.
9#[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    /// Offsets current time to given timestamp without creating an empty block.
13    /// Different to `evm_setNextBlockTimestamp` which mines a block.
14    async fn tenderly_set_next_block_timestamp(&self, timestamp: u64) -> TransportResult<u64>;
15
16    /// Set the balance of an address by sending an overwrite transaction.
17    /// Returns the transaction hash.
18    async fn tenderly_set_balance(
19        &self,
20        wallet: Address,
21        balance: U256,
22    ) -> TransportResult<FixedBytes<32>>;
23
24    /// Set the balance of multiple addresses.
25    /// Returns the transaction hash (storage override is committed via transaction).
26    async fn tenderly_set_balance_batch(
27        &self,
28        wallets: &[Address],
29        balance: U256,
30    ) -> TransportResult<FixedBytes<32>>;
31
32    /// Adds to the balance of an address
33    /// Returns the transaction hash (storage override is committed via transaction).
34    async fn tenderly_add_balance(
35        &self,
36        wallet: Address,
37        amount: U256,
38    ) -> TransportResult<FixedBytes<32>>;
39
40    /// Adds to the balance of multiple addresses
41    /// Returns the transaction hash (storage override is committed via transaction).
42    async fn tenderly_add_balance_batch(
43        &self,
44        wallets: &[Address],
45        amount: U256,
46    ) -> TransportResult<FixedBytes<32>>;
47
48    /// Sets the ERC20 balance of a wallet.
49    /// Returns the transaction hash (storage override is committed via transaction).
50    async fn tenderly_set_erc20_balance(
51        &self,
52        token: Address,
53        wallet: Address,
54        balance: U256,
55    ) -> TransportResult<FixedBytes<32>>;
56
57    /// Sets a storage slot of an address.
58    /// Returns the transaction hash (storage override is committed via transaction).
59    async fn tenderly_set_storage_at(
60        &self,
61        address: Address,
62        slot: U256,
63        value: B256,
64    ) -> TransportResult<FixedBytes<32>>;
65
66    /// Sets the code of an address.
67    /// Returns the transaction hash (storage override is committed via transaction).
68    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}