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