linera_ethereum/
client.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fmt::Debug;
5
6use async_trait::async_trait;
7use linera_alloy::{
8    primitives::{Address, Bytes, U256, U64},
9    rpc::types::eth::{
10        request::{TransactionInput, TransactionRequest},
11        BlockId, BlockNumberOrTag, Filter, Log,
12    },
13};
14use linera_base::ensure;
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16use serde_json::value::RawValue;
17
18use crate::common::{
19    event_name_from_expanded, parse_log, EthereumEvent, EthereumQueryError, EthereumServiceError,
20};
21
22/// A basic RPC client for making JSON queries
23#[async_trait]
24pub trait JsonRpcClient {
25    type Error: From<serde_json::Error> + From<EthereumQueryError>;
26
27    /// The inner function that has to be implemented and access the client
28    async fn request_inner(&self, payload: Vec<u8>) -> Result<Vec<u8>, Self::Error>;
29
30    /// Gets a new ID for the next message.
31    async fn get_id(&self) -> u64;
32
33    /// The function doing the parsing of the input and output.
34    async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
35    where
36        T: Debug + Serialize + Send + Sync,
37        R: DeserializeOwned + Send,
38    {
39        let id = self.get_id().await;
40        let payload = JsonRpcRequest::new(id, method, params);
41        let payload = serde_json::to_vec(&payload)?;
42        let body = self.request_inner(payload).await?;
43        let result = serde_json::from_slice::<JsonRpcResponse>(&body)?;
44        let raw = result.result;
45        let res = serde_json::from_str(raw.get())?;
46        ensure!(id == result.id, EthereumQueryError::IdIsNotMatching);
47        ensure!(
48            *"2.0" == result.jsonrpc,
49            EthereumQueryError::WrongJsonRpcVersion
50        );
51        Ok(res)
52    }
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56struct JsonRpcRequest<'a, T> {
57    id: u64,
58    jsonrpc: &'a str,
59    method: &'a str,
60    params: T,
61}
62
63impl<'a, T> JsonRpcRequest<'a, T> {
64    /// Creates a new JSON RPC request, the id does not matter
65    pub fn new(id: u64, method: &'a str, params: T) -> Self {
66        Self {
67            id,
68            jsonrpc: "2.0",
69            method,
70            params,
71        }
72    }
73}
74
75#[derive(Debug, Deserialize)]
76pub struct JsonRpcResponse {
77    id: u64,
78    jsonrpc: String,
79    result: Box<RawValue>,
80}
81
82/// The basic Ethereum queries that can be used from a smart contract and do not require
83/// gas to be executed.
84#[async_trait]
85pub trait EthereumQueries {
86    type Error;
87
88    /// Lists all the accounts of the Ethereum node.
89    async fn get_accounts(&self) -> Result<Vec<String>, Self::Error>;
90
91    /// Gets the latest block number of the Ethereum node.
92    async fn get_block_number(&self) -> Result<u64, Self::Error>;
93
94    /// Gets the balance of the specified address at the specified block number.
95    /// if no block number is specified then the balance of the latest block is
96    /// returned.
97    async fn get_balance(&self, address: &str, block_number: u64) -> Result<U256, Self::Error>;
98
99    /// Reads the events of the smart contract.
100    ///
101    /// This is done from a specified `contract_address` and `event_name_expanded`.
102    /// That is one should have "MyEvent(type1 indexed,type2)" instead
103    /// of the usual "MyEvent(type1,type2)"
104    ///
105    /// The `from_block` is inclusive.
106    /// The `to_block` is exclusive (contrary to Ethereum where it is inclusive)
107    async fn read_events(
108        &self,
109        contract_address: &str,
110        event_name_expanded: &str,
111        from_block: u64,
112        to_block: u64,
113    ) -> Result<Vec<EthereumEvent>, Self::Error>;
114
115    /// The operation done with `eth_call` on Ethereum returns
116    /// a result but are not committed to the blockchain. This can be useful for example
117    /// for executing function that are const and allow to inspect
118    /// the contract without modifying it.
119    async fn non_executive_call(
120        &self,
121        contract_address: &str,
122        data: Bytes,
123        from: &str,
124        block: u64,
125    ) -> Result<Bytes, Self::Error>;
126}
127
128pub(crate) fn get_block_id(block_number: u64) -> BlockId {
129    let number = BlockNumberOrTag::Number(block_number);
130    BlockId::Number(number)
131}
132
133#[async_trait]
134impl<C> EthereumQueries for C
135where
136    C: JsonRpcClient + Sync,
137    EthereumServiceError: From<<C as JsonRpcClient>::Error>,
138{
139    type Error = EthereumServiceError;
140
141    async fn get_accounts(&self) -> Result<Vec<String>, Self::Error> {
142        let results: Vec<String> = self.request("eth_accounts", ()).await?;
143        Ok(results
144            .into_iter()
145            .map(|x| x.to_lowercase())
146            .collect::<Vec<_>>())
147    }
148
149    async fn get_block_number(&self) -> Result<u64, Self::Error> {
150        let result = self.request::<_, U64>("eth_blockNumber", ()).await?;
151        Ok(result.to::<u64>())
152    }
153
154    async fn get_balance(&self, address: &str, block_number: u64) -> Result<U256, Self::Error> {
155        let address = address.parse::<Address>()?;
156        let tag = get_block_id(block_number);
157        Ok(self.request("eth_getBalance", (address, tag)).await?)
158    }
159
160    async fn read_events(
161        &self,
162        contract_address: &str,
163        event_name_expanded: &str,
164        from_block: u64,
165        to_block: u64,
166    ) -> Result<Vec<EthereumEvent>, Self::Error> {
167        let contract_address = contract_address.parse::<Address>()?;
168        let event_name = event_name_from_expanded(event_name_expanded);
169        let filter = Filter::new()
170            .address(contract_address)
171            .event(&event_name)
172            .from_block(from_block)
173            .to_block(to_block - 1);
174        let events = self
175            .request::<_, Vec<Log>>("eth_getLogs", (filter,))
176            .await?;
177        events
178            .into_iter()
179            .map(|x| parse_log(event_name_expanded, x))
180            .collect::<Result<_, _>>()
181    }
182
183    async fn non_executive_call(
184        &self,
185        contract_address: &str,
186        data: Bytes,
187        from: &str,
188        block: u64,
189    ) -> Result<Bytes, Self::Error> {
190        let contract_address = contract_address.parse::<Address>()?;
191        let from = from.parse::<Address>()?;
192        let input = TransactionInput::new(data);
193        let tx = TransactionRequest::default()
194            .from(from)
195            .to(contract_address)
196            .input(input);
197        let tag = get_block_id(block);
198        Ok(self.request::<_, Bytes>("eth_call", (tx, tag)).await?)
199    }
200}