Skip to main content

nautilus_blockchain/exchanges/parsing/pancakeswap_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    "b3159fed3ddfba67bae294599eafe2d0ec98c08bb38e0e5fb87d33154b6e05aa";
34
35sol! {
36    struct SetFeeProtocolEventData {
37        uint32 fee_protocol0_old;
38        uint32 fee_protocol1_old;
39        uint32 fee_protocol0_new;
40        uint32 fee_protocol1_new;
41    }
42}
43
44/// Parses a PancakeSwap V3 `SetFeeProtocol` event from a HyperSync log.
45///
46/// # Errors
47///
48/// Returns an error if the log parsing fails or if the event data is invalid.
49///
50/// # Panics
51///
52/// Panics if the contract address is not set in the log.
53pub fn parse_fee_protocol_update_event_hypersync(
54    dex: SharedDex,
55    log: &HypersyncLog,
56) -> anyhow::Result<FeeProtocolUpdateEvent> {
57    validate_event_signature_hash(
58        "SetFeeProtocolEvent",
59        FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH,
60        log,
61    )?;
62
63    if let Some(data) = &log.data {
64        let data_bytes = data.as_ref();
65
66        if data_bytes.len() < 4 * 32 {
67            anyhow::bail!("SetFeeProtocol event data is too short");
68        }
69
70        let decoded = match <SetFeeProtocolEventData as SolType>::abi_decode(data_bytes) {
71            Ok(decoded) => decoded,
72            Err(e) => anyhow::bail!("Failed to decode SetFeeProtocol event data: {e}"),
73        };
74
75        let pool_address = Address::from_slice(
76            log.address
77                .clone()
78                .expect("Contract address should be set in logs")
79                .as_ref(),
80        );
81        let pool_identifier = PoolIdentifier::Address(Ustr::from(&pool_address.to_string()));
82
83        Ok(FeeProtocolUpdateEvent::new(
84            dex,
85            pool_identifier,
86            extract_block_number(log)?,
87            extract_transaction_hash(log)?,
88            extract_transaction_index(log)?,
89            extract_log_index(log)?,
90            decoded.fee_protocol0_new,
91            decoded.fee_protocol1_new,
92        ))
93    } else {
94        anyhow::bail!("Missing data in SetFeeProtocol event log");
95    }
96}
97
98/// Parses a PancakeSwap V3 `SetFeeProtocol` event from an RPC log.
99///
100/// # Errors
101///
102/// Returns an error if the log parsing fails or if the event data is invalid.
103pub fn parse_fee_protocol_update_event_rpc(
104    dex: SharedDex,
105    log: &RpcLog,
106) -> anyhow::Result<FeeProtocolUpdateEvent> {
107    rpc_helpers::validate_event_signature(
108        log,
109        FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH,
110        "SetFeeProtocol",
111    )?;
112
113    let data_bytes = rpc_helpers::extract_data_bytes(log)?;
114
115    if data_bytes.len() < 4 * 32 {
116        anyhow::bail!("SetFeeProtocol event data is too short");
117    }
118
119    let decoded = match <SetFeeProtocolEventData as SolType>::abi_decode(&data_bytes) {
120        Ok(decoded) => decoded,
121        Err(e) => anyhow::bail!("Failed to decode SetFeeProtocol event data: {e}"),
122    };
123
124    let pool_address = rpc_helpers::extract_address(log)?;
125    let pool_identifier = PoolIdentifier::Address(Ustr::from(&pool_address.to_string()));
126    Ok(FeeProtocolUpdateEvent::new(
127        dex,
128        pool_identifier,
129        rpc_helpers::extract_block_number(log)?,
130        rpc_helpers::extract_transaction_hash(log)?,
131        rpc_helpers::extract_transaction_index(log)?,
132        rpc_helpers::extract_log_index(log)?,
133        decoded.fee_protocol0_new,
134        decoded.fee_protocol1_new,
135    ))
136}
137
138#[cfg(test)]
139mod tests {
140    use rstest::*;
141
142    use super::*;
143    use crate::exchanges::bsc;
144
145    const HYPERSYNC_LOG: &str =
146        include_str!("../../../../test_data/pancakeswap_v3_set_fee_protocol_hypersync.json");
147    const RPC_LOG: &str =
148        include_str!("../../../../test_data/pancakeswap_v3_set_fee_protocol_rpc.json");
149
150    #[fixture]
151    fn hypersync_log() -> HypersyncLog {
152        serde_json::from_str(HYPERSYNC_LOG).expect("Failed to deserialize HyperSync log")
153    }
154
155    #[fixture]
156    fn rpc_log() -> RpcLog {
157        serde_json::from_str(RPC_LOG).expect("Failed to deserialize RPC log")
158    }
159
160    #[rstest]
161    fn test_parse_fee_protocol_update_event_hypersync(hypersync_log: HypersyncLog) {
162        let dex = bsc::PANCAKESWAP_V3.dex.clone();
163        let event = parse_fee_protocol_update_event_hypersync(dex, &hypersync_log).unwrap();
164
165        assert_eq!(
166            event.pool_identifier.to_string(),
167            "0x172fcD41E0913e95784454622d1c3724f546f849"
168        );
169        assert_eq!(event.fee_protocol0_new, 1_000);
170        assert_eq!(event.fee_protocol1_new, 4_000);
171        assert_eq!(event.block_number, 105_475_426);
172        assert_eq!(event.transaction_index, 5);
173        assert_eq!(event.log_index, 17);
174    }
175
176    #[rstest]
177    fn test_parse_fee_protocol_update_event_rpc(rpc_log: RpcLog) {
178        let dex = bsc::PANCAKESWAP_V3.dex.clone();
179        let event = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
180
181        assert_eq!(
182            event.pool_identifier.to_string(),
183            "0x172fcD41E0913e95784454622d1c3724f546f849"
184        );
185        assert_eq!(event.fee_protocol0_new, 1_000);
186        assert_eq!(event.fee_protocol1_new, 4_000);
187        assert_eq!(event.block_number, 105_475_426);
188    }
189
190    #[rstest]
191    fn test_hypersync_rpc_match(hypersync_log: HypersyncLog, rpc_log: RpcLog) {
192        let dex = bsc::PANCAKESWAP_V3.dex.clone();
193        let event_hypersync =
194            parse_fee_protocol_update_event_hypersync(dex.clone(), &hypersync_log).unwrap();
195        let event_rpc = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
196
197        assert_eq!(event_hypersync.pool_identifier, event_rpc.pool_identifier);
198        assert_eq!(
199            event_hypersync.fee_protocol0_new,
200            event_rpc.fee_protocol0_new
201        );
202        assert_eq!(
203            event_hypersync.fee_protocol1_new,
204            event_rpc.fee_protocol1_new
205        );
206        assert_eq!(event_hypersync.block_number, event_rpc.block_number);
207        assert_eq!(event_hypersync.transaction_hash, event_rpc.transaction_hash);
208        assert_eq!(
209            event_hypersync.transaction_index,
210            event_rpc.transaction_index
211        );
212        assert_eq!(event_hypersync.log_index, event_rpc.log_index);
213    }
214}