linera_ethereum/
common.rs

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