Skip to main content

nautilus_blockchain/exchanges/parsing/uniswap_v3/
fee_protocol_update.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::fee_protocol_update::FeeProtocolUpdateEvent,
22    hypersync::{
23        HypersyncLog,
24        log::{
25            extract_block_number, extract_log_index, extract_transaction_hash,
26            extract_transaction_index, validate_event_signature_hash,
27        },
28    },
29    rpc::log as rpc_log,
30};
31
32const FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH: &str =
33    "973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b133";
34
35// Define sol macro for easier parsing of SetFeeProtocol event data.
36// All four parameters are non-indexed and live in the log data:
37// feeProtocol0Old (uint8), feeProtocol1Old (uint8), feeProtocol0New (uint8), feeProtocol1New (uint8)
38sol! {
39    struct SetFeeProtocolEventData {
40        uint8 fee_protocol0_old;
41        uint8 fee_protocol1_old;
42        uint8 fee_protocol0_new;
43        uint8 fee_protocol1_new;
44    }
45}
46
47/// Parses a `SetFeeProtocol` event from a Uniswap V3 HyperSync log.
48///
49/// # Errors
50///
51/// Returns an error if the log parsing fails or if the event data is invalid.
52///
53/// # Panics
54///
55/// Panics if the contract address is not set in the log.
56pub fn parse_fee_protocol_update_event_hypersync(
57    dex: SharedDex,
58    log: &HypersyncLog,
59) -> anyhow::Result<FeeProtocolUpdateEvent> {
60    validate_event_signature_hash(
61        "SetFeeProtocolEvent",
62        FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH,
63        log,
64    )?;
65
66    if let Some(data) = &log.data {
67        let data_bytes = data.as_ref();
68
69        // Validate the data contains 4 parameters of 32 bytes each
70        if data_bytes.len() < 4 * 32 {
71            anyhow::bail!("SetFeeProtocol event data is too short");
72        }
73
74        let decoded = match <SetFeeProtocolEventData as SolType>::abi_decode(data_bytes) {
75            Ok(decoded) => decoded,
76            Err(e) => anyhow::bail!("Failed to decode SetFeeProtocol event data: {e}"),
77        };
78
79        let pool_address = Address::from_slice(
80            log.address
81                .clone()
82                .expect("Contract address should be set in logs")
83                .as_ref(),
84        );
85        let pool_identifier = PoolIdentifier::Address(Ustr::from(&pool_address.to_string()));
86
87        Ok(FeeProtocolUpdateEvent::new(
88            dex,
89            pool_identifier,
90            extract_block_number(log)?,
91            extract_transaction_hash(log)?,
92            extract_transaction_index(log)?,
93            extract_log_index(log)?,
94            u32::from(decoded.fee_protocol0_new),
95            u32::from(decoded.fee_protocol1_new),
96        ))
97    } else {
98        anyhow::bail!("Missing data in SetFeeProtocol event log");
99    }
100}
101
102/// Parses a `SetFeeProtocol` event from an RPC log.
103///
104/// # Errors
105///
106/// Returns an error if the log parsing fails or if the event data is invalid.
107pub fn parse_fee_protocol_update_event_rpc(
108    dex: SharedDex,
109    log: &RpcLog,
110) -> anyhow::Result<FeeProtocolUpdateEvent> {
111    rpc_log::validate_event_signature(
112        log,
113        FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH,
114        "SetFeeProtocol",
115    )?;
116
117    let data_bytes = rpc_log::extract_data_bytes(log)?;
118
119    // Validate the data contains 4 parameters of 32 bytes each
120    if data_bytes.len() < 4 * 32 {
121        anyhow::bail!("SetFeeProtocol event data is too short");
122    }
123
124    let decoded = match <SetFeeProtocolEventData as SolType>::abi_decode(&data_bytes) {
125        Ok(decoded) => decoded,
126        Err(e) => anyhow::bail!("Failed to decode SetFeeProtocol event data: {e}"),
127    };
128
129    let pool_address = rpc_log::extract_address(log)?;
130    let pool_identifier = PoolIdentifier::Address(Ustr::from(&pool_address.to_string()));
131    Ok(FeeProtocolUpdateEvent::new(
132        dex,
133        pool_identifier,
134        rpc_log::extract_block_number(log)?,
135        rpc_log::extract_transaction_hash(log)?,
136        rpc_log::extract_transaction_index(log)?,
137        rpc_log::extract_log_index(log)?,
138        u32::from(decoded.fee_protocol0_new),
139        u32::from(decoded.fee_protocol1_new),
140    ))
141}
142
143#[cfg(test)]
144mod tests {
145    use std::sync::Arc;
146
147    use rstest::*;
148
149    use super::*;
150    use crate::exchanges::arbitrum;
151
152    /// Real Arbitrum on-chain `SetFeeProtocol` log at block 3,106,049 (Uniswap V3 event ABI).
153    /// Pool: 0x0d500e0f1d159e75f3771fb5e6ab86de19a8abd4
154    /// new protocol fees: feeProtocol0=6, feeProtocol1=6
155    const HYPERSYNC_LOG: &str =
156        include_str!("../../../../test_data/uniswap_v3_set_fee_protocol_hypersync.json");
157    const RPC_LOG: &str =
158        include_str!("../../../../test_data/uniswap_v3_set_fee_protocol_rpc.json");
159
160    #[fixture]
161    fn hypersync_log() -> HypersyncLog {
162        serde_json::from_str(HYPERSYNC_LOG).expect("Failed to deserialize HyperSync log")
163    }
164
165    #[fixture]
166    fn rpc_log() -> RpcLog {
167        serde_json::from_str(RPC_LOG).expect("Failed to deserialize RPC log")
168    }
169
170    #[rstest]
171    fn test_parse_fee_protocol_update_event_hypersync(hypersync_log: HypersyncLog) {
172        let dex = Arc::clone(&arbitrum::UNISWAP_V3.dex);
173        let event = parse_fee_protocol_update_event_hypersync(dex, &hypersync_log).unwrap();
174
175        assert_eq!(
176            event.pool_identifier.to_string(),
177            "0x0d500E0f1d159E75f3771Fb5e6aB86DE19A8abD4"
178        );
179        assert_eq!(event.fee_protocol0_new, 6);
180        assert_eq!(event.fee_protocol1_new, 6);
181        assert_eq!(event.block_number, 3_106_049);
182        assert_eq!(event.transaction_index, 0);
183        assert_eq!(event.log_index, 0);
184    }
185
186    #[rstest]
187    fn test_parse_fee_protocol_update_event_rpc(rpc_log: RpcLog) {
188        let dex = Arc::clone(&arbitrum::UNISWAP_V3.dex);
189        let event = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
190
191        assert_eq!(
192            event.pool_identifier.to_string(),
193            "0x0d500E0f1d159E75f3771Fb5e6aB86DE19A8abD4"
194        );
195        assert_eq!(event.fee_protocol0_new, 6);
196        assert_eq!(event.fee_protocol1_new, 6);
197        assert_eq!(event.block_number, 3_106_049);
198    }
199
200    #[rstest]
201    fn test_hypersync_rpc_match(hypersync_log: HypersyncLog, rpc_log: RpcLog) {
202        let dex = Arc::clone(&arbitrum::UNISWAP_V3.dex);
203        let event_hypersync =
204            parse_fee_protocol_update_event_hypersync(Arc::clone(&dex), &hypersync_log).unwrap();
205        let event_rpc = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
206
207        assert_eq!(event_hypersync.pool_identifier, event_rpc.pool_identifier);
208        assert_eq!(
209            event_hypersync.fee_protocol0_new,
210            event_rpc.fee_protocol0_new
211        );
212        assert_eq!(
213            event_hypersync.fee_protocol1_new,
214            event_rpc.fee_protocol1_new
215        );
216        assert_eq!(event_hypersync.block_number, event_rpc.block_number);
217        assert_eq!(event_hypersync.transaction_hash, event_rpc.transaction_hash);
218        assert_eq!(
219            event_hypersync.transaction_index,
220            event_rpc.transaction_index
221        );
222        assert_eq!(event_hypersync.log_index, event_rpc.log_index);
223    }
224}