light_compressed_pda/sdk/
event.rs1use 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 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 let borsh_serialized = event.try_to_vec().unwrap();
145
146 let mut manual_serialized = Vec::new();
148 event.man_serialize(&mut manual_serialized).unwrap();
149
150 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}