light_compressed_pda/sdk/
event.rs

1use anchor_lang::{solana_program::pubkey::Pubkey, AnchorDeserialize, AnchorSerialize};
2use std::{io::Write, mem};
3
4use super::compressed_account::CompressedAccount;
5
6#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, Default, PartialEq)]
7pub struct PublicTransactionEvent {
8    pub input_compressed_account_hashes: Vec<[u8; 32]>,
9    pub output_compressed_account_hashes: Vec<[u8; 32]>,
10    pub output_compressed_accounts: Vec<CompressedAccount>,
11    pub output_state_merkle_tree_account_indices: Vec<u8>,
12    pub output_leaf_indices: Vec<u32>,
13    pub relay_fee: Option<u64>,
14    pub is_compress: bool,
15    pub compression_lamports: Option<u64>,
16    pub pubkey_array: Vec<Pubkey>,
17    pub message: Option<Vec<u8>>,
18}
19
20pub trait SizedEvent {
21    fn event_size(&self) -> usize;
22}
23
24impl SizedEvent for PublicTransactionEvent {
25    fn event_size(&self) -> usize {
26        mem::size_of::<Self>()
27            + self.input_compressed_account_hashes.len() * mem::size_of::<[u8; 32]>()
28            + self.output_compressed_account_hashes.len() * mem::size_of::<[u8; 32]>()
29            + self.output_compressed_accounts.len() * mem::size_of::<CompressedAccount>()
30            + self.output_state_merkle_tree_account_indices.len()
31            + self.output_leaf_indices.len() * mem::size_of::<u32>()
32            + self.pubkey_array.len() * mem::size_of::<Pubkey>()
33            + self
34                .message
35                .as_ref()
36                .map(|message| message.len())
37                .unwrap_or(0)
38    }
39}
40
41impl PublicTransactionEvent {
42    pub fn man_serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
43        writer.write_all(&(self.input_compressed_account_hashes.len() as u32).to_le_bytes())?;
44        for hash in self.input_compressed_account_hashes.iter() {
45            writer.write_all(hash)?;
46        }
47
48        writer.write_all(&(self.output_compressed_account_hashes.len() as u32).to_le_bytes())?;
49        for hash in self.output_compressed_account_hashes.iter() {
50            writer.write_all(hash)?;
51        }
52
53        #[cfg(target_os = "solana")]
54        let pos = light_heap::GLOBAL_ALLOCATOR.get_heap_pos();
55        writer.write_all(&(self.output_compressed_accounts.len() as u32).to_le_bytes())?;
56        for i in 0..self.output_compressed_accounts.len() {
57            let account = self.output_compressed_accounts[i].clone();
58            account.serialize(writer)?;
59        }
60        #[cfg(target_os = "solana")]
61        light_heap::GLOBAL_ALLOCATOR.free_heap(pos);
62
63        writer.write_all(
64            &(self.output_state_merkle_tree_account_indices.len() as u32).to_le_bytes(),
65        )?;
66
67        for index in self.output_state_merkle_tree_account_indices.iter() {
68            writer.write_all(&[*index])?;
69        }
70
71        writer.write_all(&(self.output_leaf_indices.len() as u32).to_le_bytes())?;
72        for index in self.output_leaf_indices.iter() {
73            writer.write_all(&index.to_le_bytes())?;
74        }
75
76        match self.relay_fee {
77            Some(relay_fee) => {
78                writer.write_all(&[1])?;
79                writer.write_all(&relay_fee.to_le_bytes())
80            }
81            None => writer.write_all(&[0]),
82        }?;
83
84        writer.write_all(&[self.is_compress as u8])?;
85
86        match self.compression_lamports {
87            Some(compression_lamports) => {
88                writer.write_all(&[1])?;
89                writer.write_all(&compression_lamports.to_le_bytes())
90            }
91            None => writer.write_all(&[0]),
92        }?;
93
94        writer.write_all(&(self.pubkey_array.len() as u32).to_le_bytes())?;
95        for pubkey in self.pubkey_array.iter() {
96            writer.write_all(&pubkey.to_bytes())?;
97        }
98
99        match &self.message {
100            Some(message) => {
101                writer.write_all(&[1])?;
102                writer.write_all(&(message.len() as u32).to_le_bytes())?;
103                writer.write_all(message)
104            }
105            None => writer.write_all(&[0]),
106        }?;
107
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113pub mod test {
114    use super::*;
115    use crate::sdk::compressed_account::CompressedAccountData;
116    use rand::Rng;
117
118    #[test]
119    fn test_manual_vs_borsh_serialization() {
120        // Create a sample `PublicTransactionEvent` instance
121        let event = PublicTransactionEvent {
122            input_compressed_account_hashes: vec![[0u8; 32], [1u8; 32]],
123            output_compressed_account_hashes: vec![[2u8; 32], [3u8; 32]],
124            output_compressed_accounts: vec![CompressedAccount {
125                owner: Pubkey::new_unique(),
126                lamports: 100,
127                address: Some([5u8; 32]),
128                data: Some(CompressedAccountData {
129                    discriminator: [6u8; 8],
130                    data: vec![7u8; 32],
131                    data_hash: [8u8; 32],
132                }),
133            }],
134            output_state_merkle_tree_account_indices: vec![1, 2, 3],
135            output_leaf_indices: vec![4, 5, 6],
136            relay_fee: Some(1000),
137            is_compress: true,
138            compression_lamports: Some(5000),
139            pubkey_array: vec![Pubkey::new_unique(), Pubkey::new_unique()],
140            message: Some(vec![8, 9, 10]),
141        };
142
143        // Serialize using Borsh
144        let borsh_serialized = event.try_to_vec().unwrap();
145
146        // Serialize manually
147        let mut manual_serialized = Vec::new();
148        event.man_serialize(&mut manual_serialized).unwrap();
149
150        // Compare the two byte arrays
151        assert_eq!(
152            borsh_serialized, manual_serialized,
153            "Borsh and manual serialization results should match"
154        );
155    }
156
157    #[test]
158    fn test_serialization_consistency() {
159        let mut rng = rand::thread_rng();
160
161        for _ in 0..1_000_000 {
162            let input_hashes: Vec<[u8; 32]> =
163                (0..rng.gen_range(1..10)).map(|_| rng.gen()).collect();
164            let output_hashes: Vec<[u8; 32]> =
165                (0..rng.gen_range(1..10)).map(|_| rng.gen()).collect();
166            let output_accounts: Vec<CompressedAccount> = (0..rng.gen_range(1..10))
167                .map(|_| CompressedAccount {
168                    owner: Pubkey::new_unique(),
169                    lamports: rng.gen(),
170                    address: Some(rng.gen()),
171                    data: None,
172                })
173                .collect();
174            let merkle_indices: Vec<u8> = (0..rng.gen_range(1..10)).map(|_| rng.gen()).collect();
175            let leaf_indices: Vec<u32> = (0..rng.gen_range(1..10)).map(|_| rng.gen()).collect();
176            let pubkeys: Vec<Pubkey> = (0..rng.gen_range(1..10))
177                .map(|_| Pubkey::new_unique())
178                .collect();
179            let message: Option<Vec<u8>> = if rng.gen() {
180                Some((0..rng.gen_range(1..100)).map(|_| rng.gen()).collect())
181            } else {
182                None
183            };
184
185            let event = PublicTransactionEvent {
186                input_compressed_account_hashes: input_hashes,
187                output_compressed_account_hashes: output_hashes,
188                output_compressed_accounts: output_accounts,
189                output_state_merkle_tree_account_indices: merkle_indices,
190                output_leaf_indices: leaf_indices,
191                relay_fee: if rng.gen() { Some(rng.gen()) } else { None },
192                is_compress: rng.gen(),
193                compression_lamports: if rng.gen() { Some(rng.gen()) } else { None },
194                pubkey_array: pubkeys,
195                message,
196            };
197
198            let borsh_serialized = event.try_to_vec().unwrap();
199            let mut manual_serialized = Vec::new();
200            event.man_serialize(&mut manual_serialized).unwrap();
201
202            assert_eq!(
203                borsh_serialized, manual_serialized,
204                "Borsh and manual serialization results should match"
205            );
206        }
207    }
208}