ic_test/evm/
mod.rs

1use std::{
2    borrow::Cow,
3    collections::BTreeMap,
4    io::Read,
5    sync::{Arc, Mutex},
6};
7
8use alloy::{
9    network::{Ethereum, EthereumWallet, Network, TransactionBuilder},
10    primitives::{Address, BlockNumber, Bytes, B256, U128, U256, U64},
11    providers::{
12        fillers::{
13            BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
14            WalletFiller,
15        },
16        EthCall, EthCallMany, Identity, PendingTransactionBuilder, Provider, ProviderBuilder,
17        ProviderCall, RootProvider, RpcWithBlock, SendableTx,
18    },
19    rpc::{
20        client::NoParams,
21        types::{
22            erc4337::TransactionConditional,
23            simulate::{SimulatePayload, SimulatedBlock},
24            AccessListResult, Bundle, EthCallResponse, SyncStatus, TransactionRequest,
25        },
26    },
27    signers::{
28        k256::{elliptic_curve::SecretKey, Secp256k1},
29        local::PrivateKeySigner,
30    },
31    transports::TransportResult,
32};
33use alloy_node_bindings::{Anvil, AnvilInstance};
34use reqwest::Url;
35use serde_json::{json, value::RawValue};
36
37pub struct Evm {
38    rpc_url: Url,
39    anvil: AnvilInstance,
40    // This mutex is intentionally a sync mutex and not a tokio mutex.
41    users: std::sync::Mutex<BTreeMap<Address, EvmUser>>,
42}
43
44impl Evm {
45    pub fn new() -> Self {
46        Evm::default()
47    }
48
49    pub fn rpc_url(&self) -> Url {
50        self.rpc_url.clone()
51    }
52
53    pub fn chain_id(&self) -> u64 {
54        self.anvil.chain_id()
55    }
56
57    pub fn test_user_count(&self) -> usize {
58        self.anvil.addresses().len()
59    }
60
61    pub fn key(&self, index: usize) -> SecretKey<Secp256k1> {
62        self.anvil.keys()[index].clone()
63    }
64
65    pub fn test_user(&self, index: usize) -> EvmUser {
66        if index >= self.test_user_count() {
67            panic!(
68                "Reached maximum number of test users: {}",
69                self.test_user_count()
70            );
71        }
72        self.user_from(
73            self.anvil.addresses()[index],
74            self.anvil.keys()[index].clone(),
75        )
76    }
77
78    pub fn user_from(&self, address: Address, key: SecretKey<Secp256k1>) -> EvmUser {
79        let mut users = self.users.lock().unwrap();
80        if let Some(user) = users.get(&address) {
81            return user.clone();
82        }
83        let signer: PrivateKeySigner = key.clone().into();
84        let provider = ProviderBuilder::new()
85            .wallet(EthereumWallet::from(signer))
86            .on_http(self.rpc_url.clone());
87        let user = EvmUser {
88            address,
89            key,
90            provider: Arc::new(provider),
91        };
92        users.insert(user.address, user.clone());
93        user
94    }
95
96    pub fn default_user(&self) -> EvmUser {
97        self.test_user(0)
98    }
99
100    pub async fn transfer(&self, user: &EvmUser, to: Address, amount: U256) {
101        let tx = TransactionRequest::default().with_to(to).with_value(amount);
102        user.provider
103            .send_transaction(tx)
104            .await
105            .unwrap()
106            .get_receipt()
107            .await
108            .unwrap();
109    }
110
111    pub async fn get_balance(&self, addr: Address) -> U256 {
112        self.default_user()
113            .provider
114            .get_balance(addr)
115            .await
116            .unwrap()
117    }
118
119    pub async fn mine_block(&self) {
120        let response: serde_json::Value = self
121            .default_user()
122            .provider
123            .client()
124            .request("evm_mine", json!({}))
125            .await
126            .unwrap();
127        assert_eq!(response, "0x0");
128    }
129}
130
131impl Default for Evm {
132    fn default() -> Self {
133        let mut anvil = Anvil::new().keep_stdout().try_spawn().unwrap();
134        let anvil_stdout = anvil.child_mut().stdout.take();
135
136        tokio::spawn(async {
137            let mut buf = [0_u8; 4096];
138            let mut mv = anvil_stdout;
139            loop {
140                tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
141                match mv.as_mut().unwrap().read(&mut buf) {
142                    Ok(len) => {
143                        eprintln!(
144                            "{}",
145                            String::from_utf8(buf[0..len].to_vec()).unwrap_or_default()
146                        );
147                    }
148                    Err(_) => return,
149                }
150            }
151        });
152
153        let anvil_url: Url = anvil.endpoint().parse().unwrap();
154        Self {
155            rpc_url: anvil_url,
156            anvil,
157            users: Mutex::new(BTreeMap::new()),
158        }
159    }
160}
161
162pub type EvmProvider = FillProvider<
163    JoinFill<
164        JoinFill<
165            Identity,
166            JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
167        >,
168        WalletFiller<EthereumWallet>,
169    >,
170    RootProvider,
171    Ethereum,
172>;
173
174#[derive(Clone)]
175pub struct EvmUser {
176    pub address: Address,
177    pub key: SecretKey<Secp256k1>,
178    pub provider: Arc<EvmProvider>,
179}
180
181#[async_trait::async_trait]
182impl Provider<Ethereum> for EvmUser {
183    fn root(&self) -> &RootProvider {
184        self.provider.root()
185    }
186
187    fn get_accounts(&self) -> ProviderCall<NoParams, Vec<Address>> {
188        self.provider.get_accounts()
189    }
190
191    fn get_blob_base_fee(&self) -> ProviderCall<NoParams, U128, u128> {
192        self.provider.get_blob_base_fee()
193    }
194
195    fn get_block_number(&self) -> ProviderCall<NoParams, U64, BlockNumber> {
196        self.provider.get_block_number()
197    }
198
199    fn call<'req>(
200        &self,
201        tx: <Ethereum as Network>::TransactionRequest,
202    ) -> EthCall<Ethereum, Bytes> {
203        self.provider.call(tx)
204    }
205
206    fn call_many<'req>(
207        &self,
208        bundles: &'req Vec<Bundle>,
209    ) -> EthCallMany<'req, Ethereum, Vec<Vec<EthCallResponse>>> {
210        self.provider.call_many(bundles)
211    }
212
213    fn simulate<'req>(
214        &self,
215        payload: &'req SimulatePayload,
216    ) -> RpcWithBlock<
217        &'req SimulatePayload,
218        Vec<SimulatedBlock<<Ethereum as Network>::BlockResponse>>,
219    > {
220        self.provider.simulate(payload)
221    }
222
223    fn get_chain_id(&self) -> ProviderCall<NoParams, U64, u64> {
224        self.provider.get_chain_id()
225    }
226
227    fn create_access_list<'a>(
228        &self,
229        request: &'a <Ethereum as Network>::TransactionRequest,
230    ) -> RpcWithBlock<&'a <Ethereum as Network>::TransactionRequest, AccessListResult> {
231        self.provider.create_access_list(request)
232    }
233
234    async fn send_raw_transaction(
235        &self,
236        encoded_tx: &[u8],
237    ) -> TransportResult<PendingTransactionBuilder<Ethereum>> {
238        self.provider.send_raw_transaction(encoded_tx).await
239    }
240
241    async fn send_raw_transaction_conditional(
242        &self,
243        encoded_tx: &[u8],
244        conditional: TransactionConditional,
245    ) -> TransportResult<PendingTransactionBuilder<Ethereum>> {
246        self.provider
247            .send_raw_transaction_conditional(encoded_tx, conditional)
248            .await
249    }
250
251    async fn send_transaction_internal(
252        &self,
253        tx: SendableTx<Ethereum>,
254    ) -> TransportResult<PendingTransactionBuilder<Ethereum>> {
255        self.provider.send_transaction_internal(tx).await
256    }
257
258    fn syncing(&self) -> ProviderCall<NoParams, SyncStatus> {
259        self.provider.syncing()
260    }
261
262    fn get_client_version(&self) -> ProviderCall<NoParams, String> {
263        self.provider.get_client_version()
264    }
265
266    fn get_sha3(&self, data: &[u8]) -> ProviderCall<(String,), B256> {
267        self.provider.get_sha3(data)
268    }
269
270    fn get_net_version(&self) -> ProviderCall<NoParams, U64, u64> {
271        self.provider.get_net_version()
272    }
273
274    async fn raw_request_dyn(
275        &self,
276        method: Cow<'static, str>,
277        params: &RawValue,
278    ) -> TransportResult<Box<RawValue>> {
279        self.provider.raw_request_dyn(method, params).await
280    }
281
282    fn transaction_request(&self) -> <Ethereum as Network>::TransactionRequest {
283        self.provider.transaction_request()
284    }
285}