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 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
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_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}