Skip to main content

nautilus_blockchain/exchanges/parsing/uniswap_v3/
initialize.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use alloy::{dyn_abi::SolType, primitives::Address, sol};
17use nautilus_model::defi::{PoolIdentifier, SharedDex, rpc::RpcLog};
18use ustr::Ustr;
19
20use crate::{
21    events::initialize::InitializeEvent,
22    hypersync::{HypersyncLog, log::validate_event_signature_hash},
23    rpc::log as rpc_log,
24};
25
26const INITIALIZE_EVENT_SIGNATURE_HASH: &str =
27    "98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c95";
28
29// Define sol macro for easier parsing of Initialize event data
30// It contains 2 parameters:
31// sqrtPriceX96 (uint160), tick (int24)
32sol! {
33    struct InitializeEventData {
34        uint160 sqrt_price_x96;
35        int24 tick;
36    }
37}
38
39/// Parses an initialize event from a Uniswap V3 log.
40///
41/// # Errors
42///
43/// Returns an error if the log parsing fails or if the event data is invalid.
44///
45/// # Panics
46///
47/// Panics if the contract address is not set in the log.
48pub fn parse_initialize_event_hypersync(
49    dex: SharedDex,
50    log: &HypersyncLog,
51) -> anyhow::Result<InitializeEvent> {
52    validate_event_signature_hash("InitializeEvent", INITIALIZE_EVENT_SIGNATURE_HASH, log)?;
53
54    if let Some(data) = &log.data {
55        let data_bytes = data.as_ref();
56
57        // Validate if data contains 2 parameters of 32 bytes each (sqrtPriceX96 and tick)
58        if data_bytes.len() < 2 * 32 {
59            anyhow::bail!("Initialize event data is too short");
60        }
61
62        // Decode the data using the InitializeEventData struct
63        let decoded = match <InitializeEventData as SolType>::abi_decode(data_bytes) {
64            Ok(decoded) => decoded,
65            Err(e) => anyhow::bail!("Failed to decode initialize event data: {e}"),
66        };
67
68        let pool_address = Address::from_slice(
69            log.address
70                .clone()
71                .expect("Contract address should be set in logs")
72                .as_ref(),
73        );
74        let pool_identifier = PoolIdentifier::Address(Ustr::from(&pool_address.to_string()));
75
76        Ok(InitializeEvent::new(
77            dex,
78            pool_identifier,
79            decoded.sqrt_price_x96,
80            i32::try_from(decoded.tick)?,
81        ))
82    } else {
83        Err(anyhow::anyhow!("Missing data in initialize event log"))
84    }
85}
86
87/// Parses an initialize event from an RPC log.
88///
89/// # Errors
90///
91/// Returns an error if the log parsing fails or if the event data is invalid.
92pub fn parse_initialize_event_rpc(dex: SharedDex, log: &RpcLog) -> anyhow::Result<InitializeEvent> {
93    rpc_log::validate_event_signature(log, INITIALIZE_EVENT_SIGNATURE_HASH, "Initialize")?;
94
95    let data_bytes = rpc_log::extract_data_bytes(log)?;
96
97    // Validate if data contains 2 parameters of 32 bytes each (sqrtPriceX96 and tick)
98    if data_bytes.len() < 2 * 32 {
99        anyhow::bail!("Initialize event data is too short");
100    }
101
102    // Decode the data using the InitializeEventData struct
103    let decoded = match <InitializeEventData as SolType>::abi_decode(&data_bytes) {
104        Ok(decoded) => decoded,
105        Err(e) => anyhow::bail!("Failed to decode initialize event data: {e}"),
106    };
107
108    let pool_address = rpc_log::extract_address(log)?;
109    let pool_identifier = PoolIdentifier::Address(Ustr::from(&pool_address.to_string()));
110    Ok(InitializeEvent::new(
111        dex,
112        pool_identifier,
113        decoded.sqrt_price_x96,
114        i32::try_from(decoded.tick)?,
115    ))
116}
117
118#[cfg(test)]
119mod tests {
120    use std::sync::Arc;
121
122    use alloy::primitives::U160;
123    use rstest::*;
124    use serde_json::json;
125
126    use super::*;
127    use crate::exchanges::arbitrum;
128
129    /// Real HyperSync log from Arbitrum Initialize event at block 391053023
130    /// Pool: 0xd13040d4fe917ee704158cfcb3338dcd2838b245
131    /// sqrtPriceX96: 0x3d409fc4ca983d2e3df335 (large number)
132    /// tick: -139514 (0xfffddf06 as signed int24)
133    #[fixture]
134    fn hypersync_log() -> HypersyncLog {
135        let log_json = json!({
136            "removed": null,
137            "log_index": "0x4",
138            "transaction_index": "0x3",
139            "transaction_hash": "0x8f91d60156ea7a34a6bf1d411852f3ef2ad255ec84e493c9e902e4a1ff4a46af",
140            "block_hash": null,
141            "block_number": "0x175122df",
142            "address": "0xd13040d4fe917ee704158cfcb3338dcd2838b245",
143            "data": "0x0000000000000000000000000000000000000000003d409fc4ca983d2e3df335fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffddf06",
144            "topics": [
145                "0x98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c95",
146                null,
147                null,
148                null
149            ]
150        });
151        serde_json::from_value(log_json).expect("Failed to deserialize HyperSync log")
152    }
153
154    /// Real RPC log from Arbitrum Initialize event at block 391053023
155    #[fixture]
156    fn rpc_log() -> RpcLog {
157        let log_json = json!({
158            "removed": false,
159            "logIndex": "0x4",
160            "transactionIndex": "0x3",
161            "transactionHash": "0x8f91d60156ea7a34a6bf1d411852f3ef2ad255ec84e493c9e902e4a1ff4a46af",
162            "blockHash": "0xfc49f94161e2cdef8339c0b430868d64ee1f5d0bd8b8b6e45a25487958d68b25",
163            "blockNumber": "0x175122df",
164            "address": "0xd13040d4fe917ee704158cfcb3338dcd2838b245",
165            "data": "0x0000000000000000000000000000000000000000003d409fc4ca983d2e3df335fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffddf06",
166            "topics": [
167                "0x98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c95"
168            ]
169        });
170        serde_json::from_value(log_json).expect("Failed to deserialize RPC log")
171    }
172
173    #[rstest]
174    fn test_parse_initialize_event_hypersync(hypersync_log: HypersyncLog) {
175        let dex = Arc::clone(&arbitrum::UNISWAP_V3.dex);
176        let event = parse_initialize_event_hypersync(dex, &hypersync_log).unwrap();
177
178        assert_eq!(
179            event.pool_identifier.to_string(),
180            "0xd13040d4fe917EE704158CfCB3338dCd2838B245"
181        );
182        let expected_sqrt_price = U160::from_str_radix("3d409fc4ca983d2e3df335", 16).unwrap();
183        assert_eq!(event.sqrt_price_x96, expected_sqrt_price);
184        assert_eq!(event.tick, -139514);
185    }
186
187    #[rstest]
188    fn test_parse_initialize_event_rpc(rpc_log: RpcLog) {
189        let dex = Arc::clone(&arbitrum::UNISWAP_V3.dex);
190        let event = parse_initialize_event_rpc(dex, &rpc_log).unwrap();
191
192        assert_eq!(
193            event.pool_identifier.to_string(),
194            "0xd13040d4fe917EE704158CfCB3338dCd2838B245"
195        );
196        let expected_sqrt_price = U160::from_str_radix("3d409fc4ca983d2e3df335", 16).unwrap();
197        assert_eq!(event.sqrt_price_x96, expected_sqrt_price);
198        assert_eq!(event.tick, -139514);
199    }
200
201    #[rstest]
202    fn test_hypersync_rpc_match(hypersync_log: HypersyncLog, rpc_log: RpcLog) {
203        let dex = Arc::clone(&arbitrum::UNISWAP_V3.dex);
204        let event_hypersync =
205            parse_initialize_event_hypersync(Arc::clone(&dex), &hypersync_log).unwrap();
206        let event_rpc = parse_initialize_event_rpc(dex, &rpc_log).unwrap();
207
208        // Both parsers should produce identical results
209        assert_eq!(event_hypersync.pool_identifier, event_rpc.pool_identifier);
210        assert_eq!(event_hypersync.sqrt_price_x96, event_rpc.sqrt_price_x96);
211        assert_eq!(event_hypersync.tick, event_rpc.tick);
212    }
213}