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        helpers::{
25            extract_block_number, extract_log_index, extract_transaction_hash,
26            extract_transaction_index, validate_event_signature_hash,
27        },
28    },
29    rpc::helpers as rpc_helpers,
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            decoded.fee_protocol0_new,
95            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_helpers::validate_event_signature(
112        log,
113        FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH,
114        "SetFeeProtocol",
115    )?;
116
117    let data_bytes = rpc_helpers::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_helpers::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_helpers::extract_block_number(log)?,
135        rpc_helpers::extract_transaction_hash(log)?,
136        rpc_helpers::extract_transaction_index(log)?,
137        rpc_helpers::extract_log_index(log)?,
138        decoded.fee_protocol0_new,
139        decoded.fee_protocol1_new,
140    ))
141}
142
143#[cfg(test)]
144mod tests {
145    use rstest::*;
146
147    use super::*;
148    use crate::exchanges::arbitrum;
149
150    /// Real Arbitrum on-chain `SetFeeProtocol` log at block 3,106,049 (Uniswap V3 event ABI).
151    /// Pool: 0x0d500e0f1d159e75f3771fb5e6ab86de19a8abd4
152    /// new protocol fees: feeProtocol0=6, feeProtocol1=6
153    const HYPERSYNC_LOG: &str =
154        include_str!("../../../../test_data/uniswap_v3_set_fee_protocol_hypersync.json");
155    const RPC_LOG: &str =
156        include_str!("../../../../test_data/uniswap_v3_set_fee_protocol_rpc.json");
157
158    #[fixture]
159    fn hypersync_log() -> HypersyncLog {
160        serde_json::from_str(HYPERSYNC_LOG).expect("Failed to deserialize HyperSync log")
161    }
162
163    #[fixture]
164    fn rpc_log() -> RpcLog {
165        serde_json::from_str(RPC_LOG).expect("Failed to deserialize RPC log")
166    }
167
168    #[rstest]
169    fn test_parse_fee_protocol_update_event_hypersync(hypersync_log: HypersyncLog) {
170        let dex = arbitrum::UNISWAP_V3.dex.clone();
171        let event = parse_fee_protocol_update_event_hypersync(dex, &hypersync_log).unwrap();
172
173        assert_eq!(
174            event.pool_identifier.to_string(),
175            "0x0d500E0f1d159E75f3771Fb5e6aB86DE19A8abD4"
176        );
177        assert_eq!(event.fee_protocol0_new, 6);
178        assert_eq!(event.fee_protocol1_new, 6);
179        assert_eq!(event.block_number, 3_106_049);
180        assert_eq!(event.transaction_index, 0);
181        assert_eq!(event.log_index, 0);
182    }
183
184    #[rstest]
185    fn test_parse_fee_protocol_update_event_rpc(rpc_log: RpcLog) {
186        let dex = arbitrum::UNISWAP_V3.dex.clone();
187        let event = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
188
189        assert_eq!(
190            event.pool_identifier.to_string(),
191            "0x0d500E0f1d159E75f3771Fb5e6aB86DE19A8abD4"
192        );
193        assert_eq!(event.fee_protocol0_new, 6);
194        assert_eq!(event.fee_protocol1_new, 6);
195        assert_eq!(event.block_number, 3_106_049);
196    }
197
198    #[rstest]
199    fn test_hypersync_rpc_match(hypersync_log: HypersyncLog, rpc_log: RpcLog) {
200        let dex = arbitrum::UNISWAP_V3.dex.clone();
201        let event_hypersync =
202            parse_fee_protocol_update_event_hypersync(dex.clone(), &hypersync_log).unwrap();
203        let event_rpc = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
204
205        assert_eq!(event_hypersync.pool_identifier, event_rpc.pool_identifier);
206        assert_eq!(
207            event_hypersync.fee_protocol0_new,
208            event_rpc.fee_protocol0_new
209        );
210        assert_eq!(
211            event_hypersync.fee_protocol1_new,
212            event_rpc.fee_protocol1_new
213        );
214        assert_eq!(event_hypersync.block_number, event_rpc.block_number);
215        assert_eq!(event_hypersync.transaction_hash, event_rpc.transaction_hash);
216        assert_eq!(
217            event_hypersync.transaction_index,
218            event_rpc.transaction_index
219        );
220        assert_eq!(event_hypersync.log_index, event_rpc.log_index);
221    }
222}