nautilus_blockchain/exchanges/parsing/pancakeswap_v3/
fee_protocol_update.rs1use 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 "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
44pub 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
98pub fn parse_fee_protocol_update_event_rpc(
104 dex: SharedDex,
105 log: &RpcLog,
106) -> anyhow::Result<FeeProtocolUpdateEvent> {
107 rpc_log::validate_event_signature(
108 log,
109 FEE_PROTOCOL_UPDATE_EVENT_SIGNATURE_HASH,
110 "SetFeeProtocol",
111 )?;
112
113 let data_bytes = rpc_log::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_log::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_log::extract_block_number(log)?,
130 rpc_log::extract_transaction_hash(log)?,
131 rpc_log::extract_transaction_index(log)?,
132 rpc_log::extract_log_index(log)?,
133 decoded.fee_protocol0_new,
134 decoded.fee_protocol1_new,
135 ))
136}
137
138#[cfg(test)]
139mod tests {
140 use std::sync::Arc;
141
142 use rstest::*;
143
144 use super::*;
145 use crate::exchanges::bsc;
146
147 const HYPERSYNC_LOG: &str =
148 include_str!("../../../../test_data/pancakeswap_v3_set_fee_protocol_hypersync.json");
149 const RPC_LOG: &str =
150 include_str!("../../../../test_data/pancakeswap_v3_set_fee_protocol_rpc.json");
151
152 #[fixture]
153 fn hypersync_log() -> HypersyncLog {
154 serde_json::from_str(HYPERSYNC_LOG).expect("Failed to deserialize HyperSync log")
155 }
156
157 #[fixture]
158 fn rpc_log() -> RpcLog {
159 serde_json::from_str(RPC_LOG).expect("Failed to deserialize RPC log")
160 }
161
162 #[rstest]
163 fn test_parse_fee_protocol_update_event_hypersync(hypersync_log: HypersyncLog) {
164 let dex = Arc::clone(&bsc::PANCAKESWAP_V3.dex);
165 let event = parse_fee_protocol_update_event_hypersync(dex, &hypersync_log).unwrap();
166
167 assert_eq!(
168 event.pool_identifier.to_string(),
169 "0x172fcD41E0913e95784454622d1c3724f546f849"
170 );
171 assert_eq!(event.fee_protocol0_new, 1_000);
172 assert_eq!(event.fee_protocol1_new, 4_000);
173 assert_eq!(event.block_number, 105_475_426);
174 assert_eq!(event.transaction_index, 5);
175 assert_eq!(event.log_index, 17);
176 }
177
178 #[rstest]
179 fn test_parse_fee_protocol_update_event_rpc(rpc_log: RpcLog) {
180 let dex = Arc::clone(&bsc::PANCAKESWAP_V3.dex);
181 let event = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
182
183 assert_eq!(
184 event.pool_identifier.to_string(),
185 "0x172fcD41E0913e95784454622d1c3724f546f849"
186 );
187 assert_eq!(event.fee_protocol0_new, 1_000);
188 assert_eq!(event.fee_protocol1_new, 4_000);
189 assert_eq!(event.block_number, 105_475_426);
190 }
191
192 #[rstest]
193 fn test_hypersync_rpc_match(hypersync_log: HypersyncLog, rpc_log: RpcLog) {
194 let dex = Arc::clone(&bsc::PANCAKESWAP_V3.dex);
195 let event_hypersync =
196 parse_fee_protocol_update_event_hypersync(Arc::clone(&dex), &hypersync_log).unwrap();
197 let event_rpc = parse_fee_protocol_update_event_rpc(dex, &rpc_log).unwrap();
198
199 assert_eq!(event_hypersync.pool_identifier, event_rpc.pool_identifier);
200 assert_eq!(
201 event_hypersync.fee_protocol0_new,
202 event_rpc.fee_protocol0_new
203 );
204 assert_eq!(
205 event_hypersync.fee_protocol1_new,
206 event_rpc.fee_protocol1_new
207 );
208 assert_eq!(event_hypersync.block_number, event_rpc.block_number);
209 assert_eq!(event_hypersync.transaction_hash, event_rpc.transaction_hash);
210 assert_eq!(
211 event_hypersync.transaction_index,
212 event_rpc.transaction_index
213 );
214 assert_eq!(event_hypersync.log_index, event_rpc.log_index);
215 }
216}