1use super::utils::*;
4use crate::core::events::*;
5use solana_sdk::signature::Signature;
6
7pub mod discriminators {
8 pub const SWAP2_EVENT: [u8; 8] = [189, 66, 51, 168, 38, 80, 117, 153];
9 pub const SWAP2_TRANSFER_HOOK_EVENT: [u8; 8] = [134, 59, 168, 120, 94, 51, 114, 231];
10 pub const SWAP_EVENT: [u8; 8] = [27, 60, 21, 213, 138, 170, 187, 147];
11 pub const INITIALIZE_POOL_EVENT: [u8; 8] = [228, 50, 246, 85, 203, 66, 134, 37];
12 pub const CURVE_COMPLETE_EVENT: [u8; 8] = [229, 231, 86, 84, 156, 134, 75, 24];
13}
14
15pub fn parse_log(
16 log: &str,
17 signature: Signature,
18 slot: u64,
19 tx_index: u64,
20 block_time_us: Option<i64>,
21 grpc_recv_us: i64,
22) -> Option<DexEvent> {
23 let program_data = extract_program_data(log)?;
24 if program_data.len() < 8 {
25 return None;
26 }
27
28 let discriminator: [u8; 8] = program_data[0..8].try_into().ok()?;
29 let data = &program_data[8..];
30 let pool = read_pubkey(data, 0).unwrap_or_default();
31 let metadata =
32 create_metadata_simple(signature, slot, tx_index, block_time_us, pool, grpc_recv_us);
33
34 match discriminator {
35 discriminators::SWAP_EVENT => parse_swap_from_data(data, metadata),
36 discriminators::SWAP2_EVENT | discriminators::SWAP2_TRANSFER_HOOK_EVENT => {
37 parse_swap2_from_data(
38 data,
39 metadata,
40 discriminator == discriminators::SWAP2_TRANSFER_HOOK_EVENT,
41 )
42 }
43 discriminators::INITIALIZE_POOL_EVENT => parse_initialize_pool_from_data(data, metadata),
44 discriminators::CURVE_COMPLETE_EVENT => parse_curve_complete_from_data(data, metadata),
45 _ => None,
46 }
47}
48
49#[inline(always)]
50pub fn parse_swap_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
51 let mut offset = 0;
52
53 let pool = read_pubkey(data, offset)?;
54 offset += 32;
55 let config = read_pubkey(data, offset)?;
56 offset += 32;
57 let trade_direction = read_u8(data, offset)?;
58 offset += 1;
59 let has_referral = read_bool(data, offset)?;
60 offset += 1;
61 let params_amount_in = read_u64_le(data, offset)?;
62 offset += 8;
63 let minimum_amount_out = read_u64_le(data, offset)?;
64 offset += 8;
65 let actual_input_amount = read_u64_le(data, offset)?;
66 offset += 8;
67 let output_amount = read_u64_le(data, offset)?;
68 offset += 8;
69 let next_sqrt_price = read_u128_le(data, offset)?;
70 offset += 16;
71 let trading_fee = read_u64_le(data, offset)?;
72 offset += 8;
73 let protocol_fee = read_u64_le(data, offset)?;
74 offset += 8;
75 let referral_fee = read_u64_le(data, offset)?;
76 offset += 8;
77 let amount_in = read_u64_le(data, offset).unwrap_or(params_amount_in);
78 offset += 8;
79 let current_timestamp = read_u64_le(data, offset)?;
80
81 Some(DexEvent::MeteoraDbcSwap(MeteoraDbcSwapEvent {
82 third_program: None,
83 parse_status: Default::default(),
84 metadata,
85 pool,
86 config,
87 trade_direction,
88 has_referral,
89 amount_in,
90 minimum_amount_out,
91 actual_input_amount,
92 output_amount,
93 next_sqrt_price,
94 trading_fee,
95 protocol_fee,
96 referral_fee,
97 current_timestamp,
98 ..Default::default()
99 }))
100}
101
102#[inline(always)]
103pub fn parse_initialize_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
104 let mut offset = 0;
105
106 let pool = read_pubkey(data, offset)?;
107 offset += 32;
108 let config = read_pubkey(data, offset)?;
109 offset += 32;
110 let creator = read_pubkey(data, offset)?;
111 offset += 32;
112 let base_mint = read_pubkey(data, offset)?;
113 offset += 32;
114 let pool_type = read_u8(data, offset)?;
115 offset += 1;
116 let activation_point = read_u64_le(data, offset)?;
117
118 Some(DexEvent::MeteoraDbcInitializePool(MeteoraDbcInitializePoolEvent {
119 metadata,
120 pool,
121 config,
122 creator,
123 base_mint,
124 pool_type,
125 activation_point,
126 }))
127}
128
129#[inline(always)]
130pub fn parse_curve_complete_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
131 let mut offset = 0;
132
133 let pool = read_pubkey(data, offset)?;
134 offset += 32;
135 let config = read_pubkey(data, offset)?;
136 offset += 32;
137 let base_reserve = read_u64_le(data, offset)?;
138 offset += 8;
139 let quote_reserve = read_u64_le(data, offset)?;
140
141 Some(DexEvent::MeteoraDbcCurveComplete(MeteoraDbcCurveCompleteEvent {
142 metadata,
143 pool,
144 config,
145 base_reserve,
146 quote_reserve,
147 }))
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use solana_sdk::pubkey::Pubkey;
154
155 fn push_pubkey(buf: &mut Vec<u8>, byte: u8) -> Pubkey {
156 let key = Pubkey::new_from_array([byte; 32]);
157 buf.extend_from_slice(key.as_ref());
158 key
159 }
160
161 #[test]
162 fn parses_dbc_swap_layout() {
163 let mut data = Vec::new();
164 let pool = push_pubkey(&mut data, 1);
165 let config = push_pubkey(&mut data, 2);
166 data.push(1);
167 data.push(1);
168 data.extend_from_slice(&10_u64.to_le_bytes());
169 data.extend_from_slice(&9_u64.to_le_bytes());
170 data.extend_from_slice(&10_u64.to_le_bytes());
171 data.extend_from_slice(&8_u64.to_le_bytes());
172 data.extend_from_slice(&(1_u128 << 64).to_le_bytes());
173 data.extend_from_slice(&1_u64.to_le_bytes());
174 data.extend_from_slice(&2_u64.to_le_bytes());
175 data.extend_from_slice(&3_u64.to_le_bytes());
176 data.extend_from_slice(&10_u64.to_le_bytes());
177 data.extend_from_slice(&123_u64.to_le_bytes());
178
179 let event = parse_swap_from_data(&data, EventMetadata::default()).unwrap();
180 match event {
181 DexEvent::MeteoraDbcSwap(event) => {
182 assert_eq!(event.pool, pool);
183 assert_eq!(event.config, config);
184 assert_eq!(event.trade_direction, 1);
185 assert!(event.has_referral);
186 assert_eq!(event.minimum_amount_out, 9);
187 assert_eq!(event.output_amount, 8);
188 assert_eq!(event.protocol_fee, 2);
189 assert_eq!(event.current_timestamp, 123);
190 }
191 other => panic!("expected MeteoraDbcSwap, got {other:?}"),
192 }
193 }
194
195 #[test]
196 fn parses_dbc_initialize_pool_layout() {
197 let mut data = Vec::new();
198 let pool = push_pubkey(&mut data, 1);
199 let config = push_pubkey(&mut data, 2);
200 let creator = push_pubkey(&mut data, 3);
201 let base_mint = push_pubkey(&mut data, 4);
202 data.push(2);
203 data.extend_from_slice(&456_u64.to_le_bytes());
204
205 let event = parse_initialize_pool_from_data(&data, EventMetadata::default()).unwrap();
206 match event {
207 DexEvent::MeteoraDbcInitializePool(event) => {
208 assert_eq!(event.pool, pool);
209 assert_eq!(event.config, config);
210 assert_eq!(event.creator, creator);
211 assert_eq!(event.base_mint, base_mint);
212 assert_eq!(event.pool_type, 2);
213 assert_eq!(event.activation_point, 456);
214 }
215 other => panic!("expected MeteoraDbcInitializePool, got {other:?}"),
216 }
217 }
218}
219
220pub fn parse_swap2_from_data(
222 data: &[u8],
223 metadata: EventMetadata,
224 has_transfer_hook: bool,
225) -> Option<DexEvent> {
226 if data.len() < 179 {
227 return None;
228 }
229 let swap_mode = read_u8(data, 82)?;
230 let trade_direction = read_u8(data, 64)?;
231 if swap_mode > 2 || trade_direction > 1 || data[65] > 1 {
232 return None;
233 }
234 let amount_0 = read_u64_le(data, 66)?;
235 let amount_1 = read_u64_le(data, 74)?;
236 let included_fee_input_amount = read_u64_le(data, 83)?;
237 Some(DexEvent::MeteoraDbcSwap(MeteoraDbcSwapEvent {
238 third_program: None,
239 parse_status: Default::default(),
240 metadata,
241 pool: read_pubkey(data, 0)?,
242 config: read_pubkey(data, 32)?,
243 trade_direction,
244 has_referral: read_bool(data, 65)?,
245 event_version: 2,
246 swap_mode,
247 amount_0,
248 amount_1,
249 has_transfer_hook,
250 amount_in: included_fee_input_amount,
251 minimum_amount_out: if swap_mode == 2 { 0 } else { amount_1 },
252 maximum_amount_in: if swap_mode == 2 { amount_1 } else { 0 },
253 included_fee_input_amount,
254 actual_input_amount: read_u64_le(data, 91)?,
255 amount_left: read_u64_le(data, 99)?,
256 output_amount: read_u64_le(data, 107)?,
257 next_sqrt_price: read_u128_le(data, 115)?,
258 trading_fee: read_u64_le(data, 131)?,
259 protocol_fee: read_u64_le(data, 139)?,
260 referral_fee: read_u64_le(data, 147)?,
261 quote_reserve_amount: read_u64_le(data, 155)?,
262 migration_threshold: read_u64_le(data, 163)?,
263 current_timestamp: read_u64_le(data, 171)?,
264 }))
265}
266
267pub fn parse_event_cpi(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
269 if data.get(..8)? != [228, 69, 165, 46, 81, 203, 154, 29] {
270 return None;
271 }
272 let disc: [u8; 8] = data.get(8..16)?.try_into().ok()?;
273 let body = data.get(16..)?;
274 match disc {
275 discriminators::SWAP_EVENT => parse_swap_from_data(body, metadata),
276 discriminators::SWAP2_EVENT | discriminators::SWAP2_TRANSFER_HOOK_EVENT => {
277 parse_swap2_from_data(body, metadata, disc == discriminators::SWAP2_TRANSFER_HOOK_EVENT)
278 }
279 discriminators::INITIALIZE_POOL_EVENT => parse_initialize_pool_from_data(body, metadata),
280 discriminators::CURVE_COMPLETE_EVENT => parse_curve_complete_from_data(body, metadata),
281 _ => None,
282 }
283}
284
285#[cfg(test)]
286mod current_dbc_tests {
287 use super::*;
288 use base64::{engine::general_purpose::STANDARD, Engine};
289 #[test]
290 fn current_official_modes_and_transfer_hook_cpi() {
291 let fixture: serde_json::Value =
292 serde_json::from_str(include_str!("../../tests/fixtures/dbc_swap2.json")).unwrap();
293 for case in fixture["cases"].as_array().unwrap() {
294 let bytes = STANDARD.decode(case["data"].as_str().unwrap()).unwrap();
295 let mut cpi = vec![228, 69, 165, 46, 81, 203, 154, 29];
296 cpi.extend(&bytes);
297 let e = parse_event_cpi(&cpi, EventMetadata::default()).unwrap();
298 let DexEvent::MeteoraDbcSwap(e) = e else { panic!() };
299 let mode = case["mode"].as_u64().unwrap() as u8;
300 assert_eq!(e.event_version, 2);
301 assert_eq!(e.swap_mode, mode);
302 assert_eq!(e.amount_in, if mode == 0 { 100000 } else { 90000 });
303 assert_eq!(e.minimum_amount_out, if mode == 2 { 0 } else { 79000 });
304 assert_eq!(e.maximum_amount_in, if mode == 2 { 100000 } else { 0 });
305 assert_eq!(e.actual_input_amount, e.amount_in - 1000);
306 assert_eq!(e.amount_left, if mode == 1 { 10000 } else { 0 });
307 assert_eq!(e.output_amount, 80000);
308 assert_eq!(e.next_sqrt_price, (1u128 << 100) + 7);
309 assert_eq!(e.quote_reserve_amount, 9007199254740993);
310 assert_eq!(e.migration_threshold, 9007199254740995);
311 assert_eq!(e.has_transfer_hook, case["name"] == "EvtSwap2WithTransferHook");
312 for len in 0..cpi.len() {
313 assert!(parse_event_cpi(&cpi[..len], EventMetadata::default()).is_none());
314 }
315 let mut invalid = bytes[8..].to_vec();
316 invalid[82] = 3;
317 assert!(parse_swap2_from_data(&invalid, EventMetadata::default(), false).is_none());
318 }
319 }
320}