Skip to main content

linera_ethereum/
common.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Common types and helpers shared across the crate, including error types and
5//! Ethereum event parsing.
6
7use std::num::ParseIntError;
8
9#[cfg(not(target_arch = "wasm32"))]
10use alloy::rpc::json_rpc;
11use alloy::rpc::types::eth::Log;
12use alloy_primitives::{Address, B256, U256};
13use num_bigint::{BigInt, BigUint};
14use num_traits::cast::ToPrimitive;
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18/// An error that occurs when validating a JSON-RPC response.
19#[derive(Error, Debug)]
20pub enum EthereumQueryError {
21    /// The ID should be matching
22    #[error("the ID should be matching")]
23    IdIsNotMatching,
24
25    /// wrong JSON-RPC version
26    #[error("wrong JSON-RPC version")]
27    WrongJsonRpcVersion,
28}
29
30/// An error that occurs while accessing or parsing data from an Ethereum node.
31#[derive(Debug, Error)]
32pub enum EthereumServiceError {
33    /// The database is not coherent
34    #[error(transparent)]
35    EthereumQueryError(#[from] EthereumQueryError),
36
37    /// Parsing error
38    #[error(transparent)]
39    ParseIntError(#[from] ParseIntError),
40
41    /// Unsupported Ethereum type
42    #[error("Unsupported Ethereum type")]
43    UnsupportedEthereumTypeError,
44
45    /// Event parsing error
46    #[error("Event parsing error")]
47    EventParsingError,
48
49    /// Parse big int error
50    #[error(transparent)]
51    ParseBigIntError(#[from] num_bigint::ParseBigIntError),
52
53    /// Ethereum parsing error
54    #[error("Ethereum parsing error")]
55    EthereumParsingError,
56
57    /// Parse bool error
58    #[error("Parse bool error")]
59    ParseBoolError,
60
61    /// Hex parsing error
62    #[error(transparent)]
63    FromHexError(#[from] alloy_primitives::hex::FromHexError),
64
65    /// Block not found on chain
66    #[error("Block not found on chain")]
67    BlockNotFound,
68
69    /// `serde_json` error
70    #[error(transparent)]
71    JsonError(#[from] serde_json::Error),
72
73    /// RPC error
74    #[error(transparent)]
75    #[cfg(not(target_arch = "wasm32"))]
76    RpcError(#[from] json_rpc::RpcError<alloy::transports::TransportErrorKind>),
77
78    /// URL parsing error
79    #[error(transparent)]
80    #[cfg(not(target_arch = "wasm32"))]
81    UrlParseError(#[from] url::ParseError),
82
83    /// Alloy Reqwest error
84    #[error(transparent)]
85    #[cfg(not(target_arch = "wasm32"))]
86    AlloyReqwestError(#[from] alloy::transports::http::reqwest::Error),
87}
88
89/// A single primitive data type. This is used for example for the
90/// entries of Ethereum events.
91#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
92pub enum EthereumDataType {
93    /// An Ethereum address, rendered as a hexadecimal string.
94    Address(String),
95    /// A 256-bit unsigned integer.
96    Uint256(U256),
97    /// A 64-bit unsigned integer.
98    Uint64(u64),
99    /// A 64-bit signed integer.
100    Int64(i64),
101    /// A 32-bit unsigned integer.
102    Uint32(u32),
103    /// A 32-bit signed integer.
104    Int32(i32),
105    /// A 16-bit unsigned integer.
106    Uint16(u16),
107    /// A 16-bit signed integer.
108    Int16(i16),
109    /// An 8-bit unsigned integer.
110    Uint8(u8),
111    /// An 8-bit signed integer.
112    Int8(i8),
113    /// A boolean.
114    Bool(bool),
115}
116
117/// Converts an entry named
118/// `Event(type1 indexed,type2 indexed)` into `Event(type1,type2)`.
119/// `event_name_expanded` is needed for parsing the obtained log.
120pub fn event_name_from_expanded(event_name_expanded: &str) -> String {
121    event_name_expanded.replace(" indexed", "").to_string()
122}
123
124fn parse_entry(entry: B256, ethereum_type: &str) -> Result<EthereumDataType, EthereumServiceError> {
125    if ethereum_type == "address" {
126        let address = Address::from_word(entry);
127        let address = format!("{address:?}");
128        return Ok(EthereumDataType::Address(address));
129    }
130    if ethereum_type == "uint256" {
131        let entry = U256::from_be_bytes(entry.0);
132        return Ok(EthereumDataType::Uint256(entry));
133    }
134    if ethereum_type == "uint64" {
135        let entry = BigUint::from_bytes_be(&entry.0);
136        let entry = entry.to_u64().unwrap();
137        return Ok(EthereumDataType::Uint64(entry));
138    }
139    if ethereum_type == "int64" {
140        let entry = BigInt::from_signed_bytes_be(&entry.0);
141        let entry = entry.to_i64().unwrap();
142        return Ok(EthereumDataType::Int64(entry));
143    }
144    if ethereum_type == "uint32" {
145        let entry = BigUint::from_bytes_be(&entry.0);
146        let entry = entry.to_u32().unwrap();
147        return Ok(EthereumDataType::Uint32(entry));
148    }
149    if ethereum_type == "int32" {
150        let entry = BigInt::from_signed_bytes_be(&entry.0);
151        let entry = entry.to_i32().unwrap();
152        return Ok(EthereumDataType::Int32(entry));
153    }
154    if ethereum_type == "uint16" {
155        let entry = BigUint::from_bytes_be(&entry.0);
156        let entry = entry.to_u16().unwrap();
157        return Ok(EthereumDataType::Uint16(entry));
158    }
159    if ethereum_type == "int16" {
160        let entry = BigInt::from_signed_bytes_be(&entry.0);
161        let entry = entry.to_i16().unwrap();
162        return Ok(EthereumDataType::Int16(entry));
163    }
164    if ethereum_type == "uint8" {
165        let entry = BigUint::from_bytes_be(&entry.0);
166        let entry = entry.to_u8().unwrap();
167        return Ok(EthereumDataType::Uint8(entry));
168    }
169    if ethereum_type == "int8" {
170        let entry = BigInt::from_signed_bytes_be(&entry.0);
171        let entry = entry.to_i8().unwrap();
172        return Ok(EthereumDataType::Int8(entry));
173    }
174    if ethereum_type == "bool" {
175        let entry = BigUint::from_bytes_be(&entry.0);
176        let entry = entry.to_u8().unwrap();
177        let entry = match entry {
178            1 => true,
179            0 => false,
180            _ => {
181                return Err(EthereumServiceError::ParseBoolError);
182            }
183        };
184        return Ok(EthereumDataType::Bool(entry));
185    }
186    Err(EthereumServiceError::UnsupportedEthereumTypeError)
187}
188
189/// The data type for an Ethereum event emitted by a smart contract
190#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
191pub struct EthereumEvent {
192    /// The decoded values of the event's parameters.
193    pub values: Vec<EthereumDataType>,
194    /// The number of the block in which the event was emitted.
195    pub block_number: u64,
196}
197
198fn get_inner_event_type(event_name_expanded: &str) -> Result<String, EthereumServiceError> {
199    if let Some(opening_paren_index) = event_name_expanded.find('(') {
200        if let Some(closing_paren_index) = event_name_expanded.find(')') {
201            // Extract the substring between the parentheses
202            let inner_types = &event_name_expanded[opening_paren_index + 1..closing_paren_index];
203            return Ok(inner_types.to_string());
204        }
205    }
206    Err(EthereumServiceError::EventParsingError)
207}
208
209/// Parses a `log` into an [`EthereumEvent`] using the expanded event signature
210/// `event_name_expanded` (e.g. `MyEvent(type1 indexed,type2)`).
211pub fn parse_log(
212    event_name_expanded: &str,
213    log: &Log,
214) -> Result<EthereumEvent, EthereumServiceError> {
215    let inner_types = get_inner_event_type(event_name_expanded)?;
216    let ethereum_types = inner_types
217        .split(',')
218        .map(str::to_string)
219        .collect::<Vec<_>>();
220    let mut values = Vec::new();
221    let mut topic_index = 0;
222    let mut data_index = 0;
223    let mut vec = [0_u8; 32];
224    let log_data = log.data();
225    let topics = log_data.topics();
226    for ethereum_type in ethereum_types {
227        values.push(match ethereum_type.strip_suffix(" indexed") {
228            None => {
229                for (i, val) in vec.iter_mut().enumerate() {
230                    *val = log_data.data[data_index * 32 + i];
231                }
232                data_index += 1;
233                let entry = vec.into();
234                parse_entry(entry, &ethereum_type)?
235            }
236            Some(ethereum_type) => {
237                topic_index += 1;
238                parse_entry(topics[topic_index], ethereum_type)?
239            }
240        });
241    }
242    let block_number = log.block_number.unwrap();
243    Ok(EthereumEvent {
244        values,
245        block_number,
246    })
247}