Skip to main content

krusty_kms_client/
events.rs

1//! TONGO event reader — fetches and parses on-chain events.
2
3use crate::abi::tongo_events;
4use crate::types::{AEBalance, CipherBalance};
5use krusty_kms_common::{KmsError, Result};
6use starknet_rust::core::types::{BlockId, BlockTag, EmittedEvent, EventFilter};
7use starknet_rust::providers::jsonrpc::{HttpTransport, JsonRpcClient};
8use starknet_rust::providers::Provider;
9use starknet_types_core::curve::ProjectivePoint;
10use starknet_types_core::felt::Felt as CoreFelt;
11use std::sync::Arc;
12
13type StarknetRsFelt = starknet_rust::core::types::Felt;
14
15fn rs_felt_to_core(felt: StarknetRsFelt) -> CoreFelt {
16    CoreFelt::from_bytes_be(&felt.to_bytes_be())
17}
18
19fn core_felt_to_rs(felt: CoreFelt) -> StarknetRsFelt {
20    StarknetRsFelt::from_bytes_be(&felt.to_bytes_be())
21}
22
23// ── Event metadata ──────────────────────────────────────────────────────
24
25#[derive(Debug, Clone)]
26pub struct EventMetadata {
27    pub block_number: Option<u64>,
28    pub tx_hash: StarknetRsFelt,
29}
30
31fn meta(e: &EmittedEvent) -> EventMetadata {
32    EventMetadata {
33        block_number: e.block_number,
34        tx_hash: e.transaction_hash,
35    }
36}
37
38// ── Typed events ────────────────────────────────────────────────────────
39
40#[derive(Debug, Clone)]
41pub struct FundEvent {
42    pub meta: EventMetadata,
43    pub to: ProjectivePoint,
44    pub nonce: u64,
45    pub from: CoreFelt,
46    pub amount: u128,
47}
48
49#[derive(Debug, Clone)]
50pub struct OutsideFundEvent {
51    pub meta: EventMetadata,
52    pub to: ProjectivePoint,
53    pub from: CoreFelt,
54    pub amount: u128,
55}
56
57#[derive(Debug, Clone)]
58pub struct WithdrawEvent {
59    pub meta: EventMetadata,
60    pub from: ProjectivePoint,
61    pub nonce: u64,
62    pub amount: u128,
63    pub to: CoreFelt,
64}
65
66#[derive(Debug, Clone)]
67pub struct RagequitEvent {
68    pub meta: EventMetadata,
69    pub from: ProjectivePoint,
70    pub nonce: u64,
71    pub amount: u128,
72    pub to: CoreFelt,
73}
74
75#[derive(Debug, Clone)]
76pub struct RolloverEvent {
77    pub meta: EventMetadata,
78    pub to: ProjectivePoint,
79    pub nonce: u64,
80    pub rollovered: CipherBalance,
81}
82
83#[derive(Debug, Clone)]
84pub struct TransferEvent {
85    pub meta: EventMetadata,
86    pub to: ProjectivePoint,
87    pub from: ProjectivePoint,
88    pub nonce: u64,
89    pub transfer_balance: CipherBalance,
90    pub transfer_balance_self: CipherBalance,
91    pub hint_transfer: AEBalance,
92    pub hint_leftover: AEBalance,
93}
94
95#[derive(Debug, Clone)]
96pub struct BalanceDeclaredEvent {
97    pub meta: EventMetadata,
98    pub from: ProjectivePoint,
99    pub nonce: u64,
100    pub auditor_pub_key: ProjectivePoint,
101    pub declared_cipher_balance: CipherBalance,
102    pub hint: AEBalance,
103}
104
105#[derive(Debug, Clone)]
106pub struct TransferDeclaredEvent {
107    pub meta: EventMetadata,
108    pub from: ProjectivePoint,
109    pub to: ProjectivePoint,
110    pub nonce: u64,
111    pub auditor_pub_key: ProjectivePoint,
112    pub declared_cipher_balance: CipherBalance,
113    pub hint: AEBalance,
114}
115
116// ── Unified enum ────────────────────────────────────────────────────────
117
118#[derive(Debug, Clone)]
119pub enum TongoEvent {
120    Fund(FundEvent),
121    OutsideFund(OutsideFundEvent),
122    Withdraw(WithdrawEvent),
123    Ragequit(RagequitEvent),
124    Rollover(RolloverEvent),
125    Transfer(TransferEvent),
126    BalanceDeclared(BalanceDeclaredEvent),
127    TransferDeclared(TransferDeclaredEvent),
128}
129
130impl TongoEvent {
131    pub fn block_number(&self) -> Option<u64> {
132        match self {
133            Self::Fund(e) => e.meta.block_number,
134            Self::OutsideFund(e) => e.meta.block_number,
135            Self::Withdraw(e) => e.meta.block_number,
136            Self::Ragequit(e) => e.meta.block_number,
137            Self::Rollover(e) => e.meta.block_number,
138            Self::Transfer(e) => e.meta.block_number,
139            Self::BalanceDeclared(e) => e.meta.block_number,
140            Self::TransferDeclared(e) => e.meta.block_number,
141        }
142    }
143}
144
145// ── Helpers ─────────────────────────────────────────────────────────────
146
147fn parse_point(felts: &[StarknetRsFelt], offset: usize) -> Result<ProjectivePoint> {
148    if felts.len() < offset + 2 {
149        return Err(KmsError::DeserializationError(
150            "Not enough felts for point".to_string(),
151        ));
152    }
153    ProjectivePoint::from_affine(
154        rs_felt_to_core(felts[offset]),
155        rs_felt_to_core(felts[offset + 1]),
156    )
157    .map_err(|_| KmsError::DeserializationError("Invalid point in event data".to_string()))
158}
159
160fn parse_cipher_balance(felts: &[StarknetRsFelt], offset: usize) -> Result<CipherBalance> {
161    Ok(CipherBalance {
162        l: parse_point(felts, offset)?,
163        r: parse_point(felts, offset + 2)?,
164    })
165}
166
167fn felt_to_u128(felt: &StarknetRsFelt) -> u128 {
168    let bytes = felt.to_bytes_be();
169    let mut buf = [0u8; 16];
170    buf.copy_from_slice(&bytes[16..32]);
171    u128::from_be_bytes(buf)
172}
173
174fn felt_to_u64(felt: &StarknetRsFelt) -> u64 {
175    let bytes = felt.to_bytes_be();
176    let mut buf = [0u8; 8];
177    buf.copy_from_slice(&bytes[24..32]);
178    u64::from_be_bytes(buf)
179}
180
181fn get_felt(felts: &[StarknetRsFelt], index: usize) -> Result<&StarknetRsFelt> {
182    felts.get(index).ok_or_else(|| {
183        KmsError::DeserializationError(format!(
184            "Event data too short: need index {index}, got {} elements",
185            felts.len()
186        ))
187    })
188}
189
190/// Parse AEBalance from event data: 6 felts (4 for u512 ciphertext + 2 for u256 nonce).
191fn parse_ae_balance(felts: &[StarknetRsFelt], offset: usize) -> Result<AEBalance> {
192    if felts.len() < offset + 6 {
193        return Err(KmsError::DeserializationError(
194            "Not enough felts for AEBalance".to_string(),
195        ));
196    }
197
198    // Ciphertext: 4 felts → 64 bytes (each felt provides 16 bytes from low portion)
199    let mut ciphertext = [0u8; 64];
200    for i in 0..4 {
201        let bytes = felts[offset + i].to_bytes_be();
202        ciphertext[i * 16..(i + 1) * 16].copy_from_slice(&bytes[16..32]);
203    }
204
205    // Nonce: 2 felts → 24 bytes (12 bytes from each felt's low portion)
206    let mut nonce = [0u8; 24];
207    let n0 = felts[offset + 4].to_bytes_be();
208    nonce[0..12].copy_from_slice(&n0[20..32]);
209    let n1 = felts[offset + 5].to_bytes_be();
210    nonce[12..24].copy_from_slice(&n1[20..32]);
211
212    Ok(AEBalance { ciphertext, nonce })
213}
214
215// ── Reader ──────────────────────────────────────────────────────────────
216
217/// Reads and parses TONGO events from Starknet.
218pub struct TongoEventReader {
219    provider: Arc<JsonRpcClient<HttpTransport>>,
220    contract_address: StarknetRsFelt,
221}
222
223impl TongoEventReader {
224    pub fn new(provider: Arc<JsonRpcClient<HttpTransport>>, contract_address: CoreFelt) -> Self {
225        Self {
226            provider,
227            contract_address: core_felt_to_rs(contract_address),
228        }
229    }
230
231    /// Fetch raw events matching the given keys, paginating through all results.
232    async fn fetch_events(
233        &self,
234        keys: Vec<Vec<StarknetRsFelt>>,
235        from_block: Option<u64>,
236        to_block: Option<u64>,
237    ) -> Result<Vec<EmittedEvent>> {
238        let filter = EventFilter {
239            from_block: from_block.map(BlockId::Number),
240            to_block: to_block
241                .map(BlockId::Number)
242                .or(Some(BlockId::Tag(BlockTag::Latest))),
243            address: Some(self.contract_address),
244            keys: Some(keys),
245        };
246
247        let mut all_events = Vec::new();
248        let mut continuation_token: Option<String> = None;
249
250        loop {
251            let page = self
252                .provider
253                .get_events(filter.clone(), continuation_token, 100)
254                .await
255                .map_err(|e| KmsError::RpcError(e.to_string()))?;
256
257            all_events.extend(page.events);
258
259            match page.continuation_token {
260                Some(token) => continuation_token = Some(token),
261                None => break,
262            }
263        }
264
265        Ok(all_events)
266    }
267
268    // ── Per-type fetchers ───────────────────────────────────────────────
269
270    /// Fetch fund events for a public key.
271    pub async fn get_fund_events(
272        &self,
273        pub_key: &ProjectivePoint,
274        from_block: Option<u64>,
275        to_block: Option<u64>,
276    ) -> Result<Vec<FundEvent>> {
277        let (kx, ky) = point_to_rs_felts(pub_key)?;
278        let keys = vec![vec![*tongo_events::FUND_EVENT], vec![kx], vec![ky]];
279        let raw = self.fetch_events(keys, from_block, to_block).await?;
280        raw.iter().map(parse_fund_event).collect()
281    }
282
283    /// Fetch outside fund events for a public key.
284    pub async fn get_outside_fund_events(
285        &self,
286        pub_key: &ProjectivePoint,
287        from_block: Option<u64>,
288        to_block: Option<u64>,
289    ) -> Result<Vec<OutsideFundEvent>> {
290        let (kx, ky) = point_to_rs_felts(pub_key)?;
291        let keys = vec![vec![*tongo_events::OUTSIDE_FUND_EVENT], vec![kx], vec![ky]];
292        let raw = self.fetch_events(keys, from_block, to_block).await?;
293        raw.iter().map(parse_outside_fund_event).collect()
294    }
295
296    /// Fetch withdraw events for a public key.
297    pub async fn get_withdraw_events(
298        &self,
299        pub_key: &ProjectivePoint,
300        from_block: Option<u64>,
301        to_block: Option<u64>,
302    ) -> Result<Vec<WithdrawEvent>> {
303        let (kx, ky) = point_to_rs_felts(pub_key)?;
304        let keys = vec![vec![*tongo_events::WITHDRAW_EVENT], vec![kx], vec![ky]];
305        let raw = self.fetch_events(keys, from_block, to_block).await?;
306        raw.iter().map(parse_withdraw_event).collect()
307    }
308
309    /// Fetch ragequit events for a public key.
310    pub async fn get_ragequit_events(
311        &self,
312        pub_key: &ProjectivePoint,
313        from_block: Option<u64>,
314        to_block: Option<u64>,
315    ) -> Result<Vec<RagequitEvent>> {
316        let (kx, ky) = point_to_rs_felts(pub_key)?;
317        let keys = vec![vec![*tongo_events::RAGEQUIT_EVENT], vec![kx], vec![ky]];
318        let raw = self.fetch_events(keys, from_block, to_block).await?;
319        raw.iter().map(parse_ragequit_event).collect()
320    }
321
322    /// Fetch rollover events for a public key.
323    pub async fn get_rollover_events(
324        &self,
325        pub_key: &ProjectivePoint,
326        from_block: Option<u64>,
327        to_block: Option<u64>,
328    ) -> Result<Vec<RolloverEvent>> {
329        let (kx, ky) = point_to_rs_felts(pub_key)?;
330        let keys = vec![vec![*tongo_events::ROLLOVER_EVENT], vec![kx], vec![ky]];
331        let raw = self.fetch_events(keys, from_block, to_block).await?;
332        raw.iter().map(parse_rollover_event).collect()
333    }
334
335    /// Fetch transfer events where pub_key is the recipient.
336    pub async fn get_transfer_in_events(
337        &self,
338        pub_key: &ProjectivePoint,
339        from_block: Option<u64>,
340        to_block: Option<u64>,
341    ) -> Result<Vec<TransferEvent>> {
342        let (kx, ky) = point_to_rs_felts(pub_key)?;
343        let keys = vec![vec![*tongo_events::TRANSFER_EVENT], vec![kx], vec![ky]];
344        let raw = self.fetch_events(keys, from_block, to_block).await?;
345        raw.iter().map(parse_transfer_event).collect()
346    }
347
348    /// Fetch transfer events where pub_key is the sender.
349    pub async fn get_transfer_out_events(
350        &self,
351        pub_key: &ProjectivePoint,
352        from_block: Option<u64>,
353        to_block: Option<u64>,
354    ) -> Result<Vec<TransferEvent>> {
355        let (kx, ky) = point_to_rs_felts(pub_key)?;
356        // Sender keys are at positions 3,4 (skip to.x, to.y)
357        let keys = vec![
358            vec![*tongo_events::TRANSFER_EVENT],
359            vec![],
360            vec![],
361            vec![kx],
362            vec![ky],
363        ];
364        let raw = self.fetch_events(keys, from_block, to_block).await?;
365        raw.iter().map(parse_transfer_event).collect()
366    }
367
368    /// Fetch balance declared events for a public key.
369    pub async fn get_balance_declared_events(
370        &self,
371        pub_key: &ProjectivePoint,
372        from_block: Option<u64>,
373        to_block: Option<u64>,
374    ) -> Result<Vec<BalanceDeclaredEvent>> {
375        let (kx, ky) = point_to_rs_felts(pub_key)?;
376        let keys = vec![
377            vec![*tongo_events::BALANCE_DECLARED_EVENT],
378            vec![kx],
379            vec![ky],
380        ];
381        let raw = self.fetch_events(keys, from_block, to_block).await?;
382        raw.iter().map(parse_balance_declared_event).collect()
383    }
384
385    /// Fetch transfer declared events where pub_key is the sender.
386    pub async fn get_transfer_declared_from_events(
387        &self,
388        pub_key: &ProjectivePoint,
389        from_block: Option<u64>,
390        to_block: Option<u64>,
391    ) -> Result<Vec<TransferDeclaredEvent>> {
392        let (kx, ky) = point_to_rs_felts(pub_key)?;
393        let keys = vec![
394            vec![*tongo_events::TRANSFER_DECLARED_EVENT],
395            vec![kx],
396            vec![ky],
397        ];
398        let raw = self.fetch_events(keys, from_block, to_block).await?;
399        raw.iter().map(parse_transfer_declared_event).collect()
400    }
401
402    /// Fetch transfer declared events where pub_key is the recipient.
403    pub async fn get_transfer_declared_to_events(
404        &self,
405        pub_key: &ProjectivePoint,
406        from_block: Option<u64>,
407        to_block: Option<u64>,
408    ) -> Result<Vec<TransferDeclaredEvent>> {
409        let (kx, ky) = point_to_rs_felts(pub_key)?;
410        let keys = vec![
411            vec![*tongo_events::TRANSFER_DECLARED_EVENT],
412            vec![],
413            vec![],
414            vec![kx],
415            vec![ky],
416        ];
417        let raw = self.fetch_events(keys, from_block, to_block).await?;
418        raw.iter().map(parse_transfer_declared_event).collect()
419    }
420
421    /// Fetch all event types for a public key, sorted by block number (descending).
422    pub async fn get_all_events(
423        &self,
424        pub_key: &ProjectivePoint,
425        from_block: Option<u64>,
426        to_block: Option<u64>,
427    ) -> Result<Vec<TongoEvent>> {
428        let (
429            fund,
430            outside_fund,
431            withdraw,
432            ragequit,
433            rollover,
434            transfer_in,
435            transfer_out,
436            balance_declared,
437            transfer_declared_from,
438            transfer_declared_to,
439        ) = tokio::join!(
440            self.get_fund_events(pub_key, from_block, to_block),
441            self.get_outside_fund_events(pub_key, from_block, to_block),
442            self.get_withdraw_events(pub_key, from_block, to_block),
443            self.get_ragequit_events(pub_key, from_block, to_block),
444            self.get_rollover_events(pub_key, from_block, to_block),
445            self.get_transfer_in_events(pub_key, from_block, to_block),
446            self.get_transfer_out_events(pub_key, from_block, to_block),
447            self.get_balance_declared_events(pub_key, from_block, to_block),
448            self.get_transfer_declared_from_events(pub_key, from_block, to_block),
449            self.get_transfer_declared_to_events(pub_key, from_block, to_block),
450        );
451
452        let mut all: Vec<TongoEvent> = Vec::new();
453
454        for e in fund? {
455            all.push(TongoEvent::Fund(e));
456        }
457        for e in outside_fund? {
458            all.push(TongoEvent::OutsideFund(e));
459        }
460        for e in withdraw? {
461            all.push(TongoEvent::Withdraw(e));
462        }
463        for e in ragequit? {
464            all.push(TongoEvent::Ragequit(e));
465        }
466        for e in rollover? {
467            all.push(TongoEvent::Rollover(e));
468        }
469        for e in transfer_in? {
470            all.push(TongoEvent::Transfer(e));
471        }
472        for e in transfer_out? {
473            all.push(TongoEvent::Transfer(e));
474        }
475        for e in balance_declared? {
476            all.push(TongoEvent::BalanceDeclared(e));
477        }
478        for e in transfer_declared_from? {
479            all.push(TongoEvent::TransferDeclared(e));
480        }
481        for e in transfer_declared_to? {
482            all.push(TongoEvent::TransferDeclared(e));
483        }
484
485        // Sort by block number descending
486        all.sort_by_key(|e| std::cmp::Reverse(e.block_number()));
487
488        Ok(all)
489    }
490}
491
492// ── Point conversion helper ─────────────────────────────────────────────
493
494fn point_to_rs_felts(point: &ProjectivePoint) -> Result<(StarknetRsFelt, StarknetRsFelt)> {
495    let affine = point
496        .to_affine()
497        .map_err(|_| KmsError::CryptoError("Invalid public key".to_string()))?;
498    Ok((core_felt_to_rs(affine.x()), core_felt_to_rs(affine.y())))
499}
500
501// ── Event parsers ───────────────────────────────────────────────────────
502
503fn parse_fund_event(e: &EmittedEvent) -> Result<FundEvent> {
504    // keys: [selector, to.x, to.y]
505    // data: [nonce, from, amount]
506    let to = parse_point(&e.keys, 1)?;
507    Ok(FundEvent {
508        meta: meta(e),
509        to,
510        nonce: felt_to_u64(get_felt(&e.data, 0)?),
511        from: rs_felt_to_core(*get_felt(&e.data, 1)?),
512        amount: felt_to_u128(get_felt(&e.data, 2)?),
513    })
514}
515
516fn parse_outside_fund_event(e: &EmittedEvent) -> Result<OutsideFundEvent> {
517    // keys: [selector, to.x, to.y]
518    // data: [from, amount]
519    let to = parse_point(&e.keys, 1)?;
520    Ok(OutsideFundEvent {
521        meta: meta(e),
522        to,
523        from: rs_felt_to_core(*get_felt(&e.data, 0)?),
524        amount: felt_to_u128(get_felt(&e.data, 1)?),
525    })
526}
527
528fn parse_withdraw_event(e: &EmittedEvent) -> Result<WithdrawEvent> {
529    // keys: [selector, from.x, from.y]
530    // data: [nonce, amount, to]
531    let from = parse_point(&e.keys, 1)?;
532    Ok(WithdrawEvent {
533        meta: meta(e),
534        from,
535        nonce: felt_to_u64(get_felt(&e.data, 0)?),
536        amount: felt_to_u128(get_felt(&e.data, 1)?),
537        to: rs_felt_to_core(*get_felt(&e.data, 2)?),
538    })
539}
540
541fn parse_ragequit_event(e: &EmittedEvent) -> Result<RagequitEvent> {
542    // keys: [selector, from.x, from.y]
543    // data: [nonce, amount, to]
544    let from = parse_point(&e.keys, 1)?;
545    Ok(RagequitEvent {
546        meta: meta(e),
547        from,
548        nonce: felt_to_u64(get_felt(&e.data, 0)?),
549        amount: felt_to_u128(get_felt(&e.data, 1)?),
550        to: rs_felt_to_core(*get_felt(&e.data, 2)?),
551    })
552}
553
554fn parse_rollover_event(e: &EmittedEvent) -> Result<RolloverEvent> {
555    // keys: [selector, to.x, to.y]
556    // data: [nonce, rollovered (4 felts)]
557    let to = parse_point(&e.keys, 1)?;
558    Ok(RolloverEvent {
559        meta: meta(e),
560        to,
561        nonce: felt_to_u64(get_felt(&e.data, 0)?),
562        rollovered: parse_cipher_balance(&e.data, 1)?,
563    })
564}
565
566fn parse_transfer_event(e: &EmittedEvent) -> Result<TransferEvent> {
567    // keys: [selector, to.x, to.y, from.x, from.y]
568    // data: [nonce, transfer_balance(4), transfer_balance_self(4), hint_transfer(6), hint_leftover(6)]
569    let to = parse_point(&e.keys, 1)?;
570    let from = parse_point(&e.keys, 3)?;
571    Ok(TransferEvent {
572        meta: meta(e),
573        to,
574        from,
575        nonce: felt_to_u64(get_felt(&e.data, 0)?),
576        transfer_balance: parse_cipher_balance(&e.data, 1)?,
577        transfer_balance_self: parse_cipher_balance(&e.data, 5)?,
578        hint_transfer: parse_ae_balance(&e.data, 9)?,
579        hint_leftover: parse_ae_balance(&e.data, 15)?,
580    })
581}
582
583fn parse_balance_declared_event(e: &EmittedEvent) -> Result<BalanceDeclaredEvent> {
584    // keys: [selector, from.x, from.y]
585    // data: [nonce, auditor.x, auditor.y, declared(4), hint(6)]
586    let from = parse_point(&e.keys, 1)?;
587    Ok(BalanceDeclaredEvent {
588        meta: meta(e),
589        from,
590        nonce: felt_to_u64(get_felt(&e.data, 0)?),
591        auditor_pub_key: parse_point(&e.data, 1)?,
592        declared_cipher_balance: parse_cipher_balance(&e.data, 3)?,
593        hint: parse_ae_balance(&e.data, 7)?,
594    })
595}
596
597fn parse_transfer_declared_event(e: &EmittedEvent) -> Result<TransferDeclaredEvent> {
598    // keys: [selector, from.x, from.y, to.x, to.y]
599    // data: [nonce, auditor.x, auditor.y, declared(4), hint(6)]
600    let from = parse_point(&e.keys, 1)?;
601    let to = parse_point(&e.keys, 3)?;
602    Ok(TransferDeclaredEvent {
603        meta: meta(e),
604        from,
605        to,
606        nonce: felt_to_u64(get_felt(&e.data, 0)?),
607        auditor_pub_key: parse_point(&e.data, 1)?,
608        declared_cipher_balance: parse_cipher_balance(&e.data, 3)?,
609        hint: parse_ae_balance(&e.data, 7)?,
610    })
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    fn make_felt(v: u64) -> StarknetRsFelt {
618        StarknetRsFelt::from(v)
619    }
620
621    fn generator_rs() -> (StarknetRsFelt, StarknetRsFelt) {
622        let g_x = StarknetRsFelt::from_hex(
623            "0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca",
624        )
625        .unwrap();
626        let g_y = StarknetRsFelt::from_hex(
627            "0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f",
628        )
629        .unwrap();
630        (g_x, g_y)
631    }
632
633    #[test]
634    fn test_parse_fund_event() {
635        let (gx, gy) = generator_rs();
636        let event = EmittedEvent {
637            from_address: make_felt(0x999),
638            keys: vec![*tongo_events::FUND_EVENT, gx, gy],
639            data: vec![make_felt(1), make_felt(0xABC), make_felt(500)],
640            block_hash: None,
641            block_number: Some(100),
642            transaction_hash: make_felt(0xDEAD),
643            event_index: 0,
644            transaction_index: 0,
645        };
646
647        let parsed = parse_fund_event(&event).unwrap();
648        assert_eq!(parsed.nonce, 1);
649        assert_eq!(parsed.amount, 500);
650        assert_eq!(parsed.meta.block_number, Some(100));
651    }
652
653    #[test]
654    fn test_parse_outside_fund_event() {
655        let (gx, gy) = generator_rs();
656        let event = EmittedEvent {
657            from_address: make_felt(0x999),
658            keys: vec![*tongo_events::OUTSIDE_FUND_EVENT, gx, gy],
659            data: vec![make_felt(0xABC), make_felt(1000)],
660            block_hash: None,
661            block_number: Some(200),
662            transaction_hash: make_felt(0xBEEF),
663            event_index: 0,
664            transaction_index: 0,
665        };
666
667        let parsed = parse_outside_fund_event(&event).unwrap();
668        assert_eq!(parsed.amount, 1000);
669    }
670
671    #[test]
672    fn test_parse_withdraw_event() {
673        let (gx, gy) = generator_rs();
674        let event = EmittedEvent {
675            from_address: make_felt(0x999),
676            keys: vec![*tongo_events::WITHDRAW_EVENT, gx, gy],
677            data: vec![make_felt(5), make_felt(250), make_felt(0x123)],
678            block_hash: None,
679            block_number: Some(300),
680            transaction_hash: make_felt(0xCAFE),
681            event_index: 0,
682            transaction_index: 0,
683        };
684
685        let parsed = parse_withdraw_event(&event).unwrap();
686        assert_eq!(parsed.nonce, 5);
687        assert_eq!(parsed.amount, 250);
688    }
689
690    #[test]
691    fn test_tongo_event_sorting() {
692        let e1 = TongoEvent::Fund(FundEvent {
693            meta: EventMetadata {
694                block_number: Some(100),
695                tx_hash: make_felt(1),
696            },
697            to: krusty_kms_crypto::StarkCurve::generator(),
698            nonce: 0,
699            from: CoreFelt::ZERO,
700            amount: 0,
701        });
702        let e2 = TongoEvent::Fund(FundEvent {
703            meta: EventMetadata {
704                block_number: Some(200),
705                tx_hash: make_felt(2),
706            },
707            to: krusty_kms_crypto::StarkCurve::generator(),
708            nonce: 0,
709            from: CoreFelt::ZERO,
710            amount: 0,
711        });
712
713        let mut events = [e1, e2];
714        events.sort_by_key(|e| std::cmp::Reverse(e.block_number()));
715        assert_eq!(events[0].block_number(), Some(200));
716        assert_eq!(events[1].block_number(), Some(100));
717    }
718
719    /// Regression: parsers must return Err (not panic) on short event data.
720    #[test]
721    fn test_parsers_return_err_on_short_data() {
722        let (gx, gy) = generator_rs();
723        let base = EmittedEvent {
724            from_address: make_felt(0x999),
725            keys: vec![make_felt(0), gx, gy, gx, gy],
726            data: vec![],
727            block_hash: None,
728            block_number: Some(1),
729            transaction_hash: make_felt(0x1),
730            event_index: 0,
731            transaction_index: 0,
732        };
733
734        assert!(parse_fund_event(&base).is_err());
735        assert!(parse_outside_fund_event(&base).is_err());
736        assert!(parse_withdraw_event(&base).is_err());
737        assert!(parse_ragequit_event(&base).is_err());
738        assert!(parse_rollover_event(&base).is_err());
739        assert!(parse_transfer_event(&base).is_err());
740        assert!(parse_balance_declared_event(&base).is_err());
741        assert!(parse_transfer_declared_event(&base).is_err());
742    }
743}