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