nautilus_blockchain/exchanges/parsing/uniswap_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 "973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b133";
34
35sol! {
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
47pub 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 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
102pub 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 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 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}