linera_ethereum/test_utils/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//use crate::test_utils::linera_alloy_sol_types;
5use linera_alloy::{
6    network::{Ethereum, EthereumSigner},
7    node_bindings::{Anvil, AnvilInstance},
8    primitives::{Address, U256},
9    providers::{
10        fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, SignerFiller},
11        ProviderBuilder, RootProvider,
12    },
13    signers::{wallet::LocalWallet, Signer},
14    sol,
15    transports::http::reqwest::Client,
16};
17use linera_base::port::get_free_port;
18use url::Url;
19
20use crate::{
21    client::EthereumQueries,
22    provider::{EthereumClient, EthereumClientSimplified, HttpProvider},
23};
24
25sol!(
26    #[allow(missing_docs)]
27    #[sol(rpc)]
28    SimpleTokenContract,
29    "./contracts/SimpleToken.json"
30);
31
32sol!(
33    #[allow(missing_docs)]
34    #[sol(rpc)]
35    EventNumericsContract,
36    "./contracts/EventNumerics.json"
37);
38
39#[expect(clippy::type_complexity)]
40pub struct AnvilTest {
41    pub anvil_instance: AnvilInstance,
42    pub endpoint: String,
43    pub ethereum_client: EthereumClient<HttpProvider>,
44    pub wallet_info: (LocalWallet, String),
45    pub rpc_url: Url,
46    pub provider: FillProvider<
47        JoinFill<
48            JoinFill<
49                JoinFill<JoinFill<linera_alloy::providers::Identity, GasFiller>, NonceFiller>,
50                ChainIdFiller,
51            >,
52            SignerFiller<EthereumSigner>,
53        >,
54        RootProvider<linera_alloy::transports::http::Http<Client>>,
55        linera_alloy::transports::http::Http<Client>,
56        Ethereum,
57    >,
58}
59
60pub async fn get_anvil() -> anyhow::Result<AnvilTest> {
61    let port = get_free_port().await?;
62    let anvil_instance = Anvil::new().port(port).try_spawn()?;
63    let index = 0;
64    let wallet: LocalWallet = anvil_instance.keys()[index].clone().into();
65    let address = format!("{:?}", anvil_instance.addresses()[index]);
66    let wallet_info = (wallet.clone(), address);
67    let endpoint = anvil_instance.endpoint();
68    let ethereum_client = EthereumClient::new(endpoint.clone())?;
69    let rpc_url = Url::parse(&endpoint)?;
70    let provider = ProviderBuilder::new()
71        .with_recommended_fillers()
72        .signer(EthereumSigner::from(wallet))
73        .on_http(rpc_url.clone());
74    Ok(AnvilTest {
75        anvil_instance,
76        endpoint,
77        ethereum_client,
78        wallet_info,
79        rpc_url,
80        provider,
81    })
82}
83
84impl AnvilTest {
85    pub fn get_wallet(&self, index: usize) -> (LocalWallet, String) {
86        let address = self.anvil_instance.addresses()[index];
87        let address = format!("{:?}", address);
88        let wallet: LocalWallet = self.anvil_instance.keys()[index].clone().into();
89        let wallet = wallet.with_chain_id(Some(self.anvil_instance.chain_id()));
90        (wallet, address)
91    }
92
93    pub fn get_address(&self, index: usize) -> String {
94        let address = self.anvil_instance.addresses()[index];
95        format!("{:?}", address)
96    }
97}
98
99pub struct SimpleTokenContractFunction {
100    pub contract_address: String,
101    pub anvil_test: AnvilTest,
102}
103
104impl SimpleTokenContractFunction {
105    pub async fn new(anvil_test: AnvilTest) -> anyhow::Result<Self> {
106        // 2: initializing the contract
107        let initial_supply = U256::from(1000);
108        let simple_token =
109            SimpleTokenContract::deploy(&anvil_test.provider, initial_supply).await?;
110        let contract_address = simple_token.address();
111        let contract_address = format!("{:?}", contract_address);
112        Ok(Self {
113            contract_address,
114            anvil_test,
115        })
116    }
117
118    // Only the balanceOf operation is of interest for this contract
119    pub async fn balance_of(&self, to: &str, block: u64) -> anyhow::Result<U256> {
120        // Getting the simple_token
121        let contract_address = self.contract_address.parse::<Address>()?;
122        let simple_token =
123            SimpleTokenContract::new(contract_address, self.anvil_test.provider.clone());
124        // Creating the calldata
125        let to_address = to.parse::<Address>()?;
126        let data = simple_token.balanceOf(to_address).calldata().clone();
127        // Doing the check using the anvil_test provider
128        let answer = self
129            .anvil_test
130            .ethereum_client
131            .non_executive_call(&self.contract_address, data.clone(), to, block)
132            .await?;
133        // Using the Ethereum client simplified.
134        let ethereum_client_simp = EthereumClientSimplified::new(self.anvil_test.endpoint.clone());
135        let answer_simp = ethereum_client_simp
136            .non_executive_call(&self.contract_address, data, to, block)
137            .await?;
138        assert_eq!(answer_simp, answer);
139        // Converting the output
140        let mut vec = [0_u8; 32];
141        for (i, val) in vec.iter_mut().enumerate() {
142            *val = answer.0[i];
143        }
144        let balance = U256::from_be_bytes(vec);
145        Ok(balance)
146    }
147
148    pub async fn transfer(&self, from: &str, to: &str, value: U256) -> anyhow::Result<()> {
149        // Getting the simple_token
150        let contract_address = self.contract_address.parse::<Address>()?;
151        let to_address = to.parse::<Address>()?;
152        let from_address = from.parse::<Address>()?;
153        let simple_token =
154            SimpleTokenContract::new(contract_address, self.anvil_test.provider.clone());
155        // Doing the transfer
156        let builder = simple_token.transfer(to_address, value).from(from_address);
157        let _receipt = builder.send().await?.get_receipt().await?;
158        Ok(())
159    }
160}
161
162pub struct EventNumericsContractFunction {
163    pub contract_address: String,
164    pub anvil_test: AnvilTest,
165}
166
167impl EventNumericsContractFunction {
168    pub async fn new(anvil_test: AnvilTest) -> anyhow::Result<Self> {
169        // Deploying the event numerics contract
170        let initial_supply = U256::from(0);
171        let event_numerics =
172            EventNumericsContract::deploy(&anvil_test.provider, initial_supply).await?;
173        // Getting the contract address
174        let contract_address = event_numerics.address();
175        let contract_address = format!("{:?}", contract_address);
176        Ok(Self {
177            contract_address,
178            anvil_test,
179        })
180    }
181}