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