linera_ethereum/test_utils/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use alloy::{
5    network::{Ethereum, EthereumWallet},
6    node_bindings::{Anvil, AnvilInstance},
7    providers::{
8        fillers::{
9            BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
10            WalletFiller,
11        },
12        ProviderBuilder, RootProvider,
13    },
14    signers::local::PrivateKeySigner,
15    sol,
16};
17use alloy_primitives::{Address, U256};
18use linera_base::port::get_free_port;
19use url::Url;
20
21use crate::{
22    client::EthereumQueries, common::EthereumServiceError, provider::EthereumClientSimplified,
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#[allow(clippy::type_complexity)]
40#[derive(Clone)]
41pub struct EthereumClient {
42    pub provider: FillProvider<
43        JoinFill<
44            JoinFill<
45                alloy::providers::Identity,
46                JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
47            >,
48            WalletFiller<EthereumWallet>,
49        >,
50        RootProvider<Ethereum>,
51    >,
52}
53
54impl EthereumClient {
55    /// Connects to an existing Ethereum node and creates an `EthereumClient`
56    /// if successful.
57    pub fn new(url: String) -> Result<Self, EthereumServiceError> {
58        let rpc_url = Url::parse(&url)?;
59        // this address is in the anvil test.
60        let pk: PrivateKeySigner =
61            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
62                .parse()
63                .unwrap();
64        let wallet = EthereumWallet::from(pk);
65        let provider = ProviderBuilder::new()
66            .wallet(wallet.clone())
67            .connect_http(rpc_url);
68        let endpoint = Self { provider };
69        Ok(endpoint)
70    }
71}
72
73pub struct AnvilTest {
74    pub anvil_instance: AnvilInstance,
75    pub endpoint: String,
76    pub ethereum_client: EthereumClient,
77    pub rpc_url: Url,
78}
79
80pub async fn get_anvil() -> anyhow::Result<AnvilTest> {
81    let port = get_free_port().await?;
82    let anvil_instance = Anvil::new().port(port).try_spawn()?;
83    let endpoint = anvil_instance.endpoint();
84    let ethereum_client = EthereumClient::new(endpoint.clone())?;
85    let rpc_url = Url::parse(&endpoint)?;
86    Ok(AnvilTest {
87        anvil_instance,
88        endpoint,
89        ethereum_client,
90        rpc_url,
91    })
92}
93
94impl AnvilTest {
95    pub fn get_address(&self, index: usize) -> String {
96        let address = self.anvil_instance.addresses()[index];
97        format!("{:?}", address)
98    }
99}
100
101pub struct SimpleTokenContractFunction {
102    pub contract_address: String,
103    pub anvil_test: AnvilTest,
104}
105
106impl SimpleTokenContractFunction {
107    pub async fn new(anvil_test: AnvilTest) -> anyhow::Result<Self> {
108        // 2: initializing the contract
109        let initial_supply = U256::from(1000);
110        let simple_token =
111            SimpleTokenContract::deploy(&anvil_test.ethereum_client.provider, initial_supply)
112                .await?;
113        let contract_address = simple_token.address();
114        let contract_address = format!("{:?}", contract_address);
115        Ok(Self {
116            contract_address,
117            anvil_test,
118        })
119    }
120
121    // Only the balanceOf operation is of interest for this contract
122    pub async fn balance_of(&self, to: &str, block: u64) -> anyhow::Result<U256> {
123        // Getting the simple_token
124        let contract_address = self.contract_address.parse::<Address>()?;
125        let simple_token = SimpleTokenContract::new(
126            contract_address,
127            self.anvil_test.ethereum_client.provider.clone(),
128        );
129        // Creating the calldata
130        let to_address = to.parse::<Address>()?;
131        let data = simple_token.balanceOf(to_address).calldata().clone();
132        // Using the Ethereum client simplified.
133        let ethereum_client_simp = EthereumClientSimplified::new(self.anvil_test.endpoint.clone());
134        let answer = ethereum_client_simp
135            .non_executive_call(&self.contract_address, data, to, block)
136            .await?;
137        // Converting the output
138        let mut vec = [0_u8; 32];
139        for (i, val) in vec.iter_mut().enumerate() {
140            *val = answer.0[i];
141        }
142        let balance = U256::from_be_bytes(vec);
143        Ok(balance)
144    }
145
146    pub async fn transfer(&self, from: &str, to: &str, value: U256) -> anyhow::Result<()> {
147        // Getting the simple_token
148        let contract_address = self.contract_address.parse::<Address>()?;
149        let to_address = to.parse::<Address>()?;
150        let from_address = from.parse::<Address>()?;
151        let simple_token = SimpleTokenContract::new(
152            contract_address,
153            self.anvil_test.ethereum_client.provider.clone(),
154        );
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.ethereum_client.provider, initial_supply)
173                .await?;
174        // Getting the contract address
175        let contract_address = event_numerics.address();
176        let contract_address = format!("{:?}", contract_address);
177        Ok(Self {
178            contract_address,
179            anvil_test,
180        })
181    }
182}