light_program_test/program_test/
light_program_test.rs

1use std::fmt::{self, Debug, Formatter};
2
3use account_compression::{AddressMerkleTreeAccount, QueueAccount};
4use light_client::{
5    indexer::{AddressMerkleTreeAccounts, StateMerkleTreeAccounts},
6    rpc::{merkle_tree::MerkleTreeExt, RpcError},
7};
8use light_compressed_account::hash_to_bn254_field_size_be;
9use light_prover_client::prover::{spawn_prover, ProverConfig};
10use litesvm::LiteSVM;
11use solana_account::WritableAccount;
12use solana_sdk::signature::{Keypair, Signer};
13
14use crate::{
15    accounts::{
16        initialize::initialize_accounts, test_accounts::TestAccounts, test_keypairs::TestKeypairs,
17    },
18    indexer::TestIndexer,
19    program_test::TestRpc,
20    utils::setup_light_programs::setup_light_programs,
21    ProgramTestConfig,
22};
23
24pub struct LightProgramTest {
25    pub config: ProgramTestConfig,
26    pub context: LiteSVM,
27    pub indexer: Option<TestIndexer>,
28    pub test_accounts: TestAccounts,
29    pub payer: Keypair,
30    pub transaction_counter: usize,
31}
32
33impl LightProgramTest {
34    /// Creates ProgramTestContext with light protocol and additional programs.
35    ///
36    /// Programs:
37    /// 1. light program
38    /// 2. account_compression program
39    /// 3. light_compressed_token program
40    /// 4. light_system_program program
41    ///
42    /// Light Protocol accounts:
43    /// 5. creates and initializes governance authority
44    /// 6. creates and initializes group authority
45    /// 7. registers the light_system_program program with the group authority
46    /// 8. initializes Merkle tree owned by
47    /// Note:
48    /// - registers a forester
49    /// - advances to the active phase slot 2
50    /// - active phase doesn't end
51    pub async fn new(config: ProgramTestConfig) -> Result<LightProgramTest, RpcError> {
52        let mut context = setup_light_programs(config.additional_programs.clone())?;
53        let payer = Keypair::new();
54        context
55            .airdrop(&payer.pubkey(), 100_000_000_000_000)
56            .expect("Payer airdrop failed.");
57        let mut context = Self {
58            context,
59            indexer: None,
60            test_accounts: TestAccounts::get_program_test_test_accounts(),
61            payer,
62            config: config.clone(),
63            transaction_counter: 0,
64        };
65        let keypairs = TestKeypairs::program_test_default();
66
67        context
68            .context
69            .airdrop(&keypairs.governance_authority.pubkey(), 100_000_000_000_000)
70            .expect("governance_authority airdrop failed.");
71        context
72            .context
73            .airdrop(&keypairs.forester.pubkey(), 10_000_000_000)
74            .expect("forester airdrop failed.");
75
76        if !config.skip_protocol_init {
77            let restore_logs = context.config.no_logs;
78            if context.config.skip_startup_logs {
79                context.config.no_logs = true;
80            }
81            initialize_accounts(&mut context, &config, &keypairs).await?;
82            if context.config.skip_startup_logs {
83                context.config.no_logs = restore_logs;
84            }
85            let batch_size = config
86                .v2_state_tree_config
87                .as_ref()
88                .map(|config| config.output_queue_batch_size as usize);
89            let test_accounts = context.test_accounts.clone();
90            context.add_indexer(&test_accounts, batch_size).await?;
91
92            // ensure that address tree pubkey is amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2
93            {
94                let address_mt = context.test_accounts.v1_address_trees[0].merkle_tree;
95                let address_queue_pubkey = context.test_accounts.v1_address_trees[0].queue;
96                let mut account = context
97                    .context
98                    .get_account(&keypairs.address_merkle_tree.pubkey())
99                    .unwrap();
100                let merkle_tree_account = bytemuck::from_bytes_mut::<AddressMerkleTreeAccount>(
101                    &mut account.data_as_mut_slice()[8..AddressMerkleTreeAccount::LEN],
102                );
103                merkle_tree_account.metadata.associated_queue = address_queue_pubkey.into();
104                context.set_account(address_mt, account);
105
106                let mut account = context
107                    .context
108                    .get_account(&keypairs.address_merkle_tree_queue.pubkey())
109                    .unwrap();
110                let queue_account = bytemuck::from_bytes_mut::<QueueAccount>(
111                    &mut account.data_as_mut_slice()[8..QueueAccount::LEN],
112                );
113                queue_account.metadata.associated_merkle_tree = address_mt.into();
114                context.set_account(address_queue_pubkey, account);
115            }
116        }
117        // Copy v1 state merkle tree accounts to devnet pubkeys
118        {
119            let tree_account = context
120                .context
121                .get_account(&keypairs.state_merkle_tree.pubkey());
122            let queue_account = context
123                .context
124                .get_account(&keypairs.nullifier_queue.pubkey());
125            let cpi_account = context
126                .context
127                .get_account(&keypairs.cpi_context_account.pubkey());
128
129            if let (Some(tree_acc), Some(queue_acc), Some(cpi_acc)) =
130                (tree_account, queue_account, cpi_account)
131            {
132                for i in 0..context.test_accounts.v1_state_trees.len() {
133                    let state_mt = context.test_accounts.v1_state_trees[i].merkle_tree;
134                    let nullifier_queue_pubkey =
135                        context.test_accounts.v1_state_trees[i].nullifier_queue;
136                    let cpi_context_pubkey = context.test_accounts.v1_state_trees[i].cpi_context;
137
138                    // Update tree account with correct associated queue
139                    let mut tree_account_data = tree_acc.clone();
140                    {
141                        let merkle_tree_account = bytemuck::from_bytes_mut::<
142                            account_compression::StateMerkleTreeAccount,
143                        >(
144                            &mut tree_account_data.data_as_mut_slice()
145                                [8..account_compression::StateMerkleTreeAccount::LEN],
146                        );
147                        merkle_tree_account.metadata.associated_queue =
148                            nullifier_queue_pubkey.into();
149                    }
150                    context.set_account(state_mt, tree_account_data);
151
152                    // Update queue account with correct associated merkle tree
153                    let mut queue_account_data = queue_acc.clone();
154                    {
155                        let queue_account = bytemuck::from_bytes_mut::<QueueAccount>(
156                            &mut queue_account_data.data_as_mut_slice()[8..QueueAccount::LEN],
157                        );
158                        queue_account.metadata.associated_merkle_tree = state_mt.into();
159                    }
160                    context.set_account(nullifier_queue_pubkey, queue_account_data);
161
162                    // Update CPI context account with correct associated merkle tree and queue
163                    let mut cpi_account_data = cpi_acc.clone();
164                    {
165                        let associated_merkle_tree_offset = 8 + 32; // discriminator + fee_payer
166                        let associated_queue_offset = 8 + 32 + 32; // discriminator + fee_payer + associated_merkle_tree
167                        cpi_account_data.data_as_mut_slice()
168                            [associated_merkle_tree_offset..associated_merkle_tree_offset + 32]
169                            .copy_from_slice(&state_mt.to_bytes());
170                        cpi_account_data.data_as_mut_slice()
171                            [associated_queue_offset..associated_queue_offset + 32]
172                            .copy_from_slice(&nullifier_queue_pubkey.to_bytes());
173                    }
174                    context.set_account(cpi_context_pubkey, cpi_account_data);
175                }
176            }
177        }
178        {
179            let address_mt = context.test_accounts.v2_address_trees[0];
180            let account = context
181                .context
182                .get_account(&keypairs.batch_address_merkle_tree.pubkey());
183            if let Some(account) = account {
184                context.set_account(address_mt, account);
185            }
186        }
187        // Copy batched state merkle tree accounts to devnet pubkeys
188        {
189            let tree_account = context
190                .context
191                .get_account(&keypairs.batched_state_merkle_tree.pubkey());
192            let queue_account = context
193                .context
194                .get_account(&keypairs.batched_output_queue.pubkey());
195            let cpi_account = context
196                .context
197                .get_account(&keypairs.batched_cpi_context.pubkey());
198
199            if let (Some(tree_acc), Some(queue_acc), Some(cpi_acc)) =
200                (tree_account, queue_account, cpi_account)
201            {
202                use light_batched_merkle_tree::{
203                    merkle_tree::BatchedMerkleTreeAccount, queue::BatchedQueueAccount,
204                };
205
206                for i in 0..context.test_accounts.v2_state_trees.len() {
207                    let merkle_tree_pubkey = context.test_accounts.v2_state_trees[i].merkle_tree;
208                    let output_queue_pubkey = context.test_accounts.v2_state_trees[i].output_queue;
209                    let cpi_context_pubkey = context.test_accounts.v2_state_trees[i].cpi_context;
210
211                    // Update tree account with correct associated queue and hashed pubkey
212                    let mut tree_account_data = tree_acc.clone();
213                    {
214                        let mut tree = BatchedMerkleTreeAccount::state_from_bytes(
215                            tree_account_data.data_as_mut_slice(),
216                            &merkle_tree_pubkey.into(),
217                        )
218                        .unwrap();
219                        let metadata = tree.get_metadata_mut();
220                        metadata.metadata.associated_queue = output_queue_pubkey.into();
221                        metadata.hashed_pubkey =
222                            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
223                    }
224                    context.set_account(merkle_tree_pubkey, tree_account_data);
225
226                    // Update queue account with correct associated merkle tree and hashed pubkeys
227                    let mut queue_account_data = queue_acc.clone();
228                    {
229                        let mut queue = BatchedQueueAccount::output_from_bytes(
230                            queue_account_data.data_as_mut_slice(),
231                        )
232                        .unwrap();
233                        let metadata = queue.get_metadata_mut();
234                        metadata.metadata.associated_merkle_tree = merkle_tree_pubkey.into();
235                        metadata.hashed_merkle_tree_pubkey =
236                            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
237                        metadata.hashed_queue_pubkey =
238                            hash_to_bn254_field_size_be(&output_queue_pubkey.to_bytes());
239                    }
240                    context.set_account(output_queue_pubkey, queue_account_data);
241
242                    // Update CPI context account with correct associated merkle tree and queue
243                    let mut cpi_account_data = cpi_acc.clone();
244                    {
245                        let associated_merkle_tree_offset = 8 + 32; // discriminator + fee_payer
246                        let associated_queue_offset = 8 + 32 + 32; // discriminator + fee_payer + associated_merkle_tree
247                        cpi_account_data.data_as_mut_slice()
248                            [associated_merkle_tree_offset..associated_merkle_tree_offset + 32]
249                            .copy_from_slice(&merkle_tree_pubkey.to_bytes());
250                        cpi_account_data.data_as_mut_slice()
251                            [associated_queue_offset..associated_queue_offset + 32]
252                            .copy_from_slice(&output_queue_pubkey.to_bytes());
253                    }
254                    context.set_account(cpi_context_pubkey, cpi_account_data);
255                }
256            }
257        }
258
259        // reset tx counter after program setup.
260        context.transaction_counter = 0;
261        // Will always start a prover server.
262        #[cfg(feature = "devenv")]
263        let prover_config = if config.prover_config.is_none() {
264            Some(ProverConfig::default())
265        } else {
266            config.prover_config
267        };
268        #[cfg(not(feature = "devenv"))]
269        let prover_config = if config.with_prover && config.prover_config.is_none() {
270            Some(ProverConfig::default())
271        } else {
272            config.prover_config
273        };
274        if let Some(ref prover_config) = prover_config {
275            spawn_prover(prover_config.clone()).await;
276        }
277        Ok(context)
278    }
279
280    pub fn indexer(&self) -> Result<&TestIndexer, RpcError> {
281        self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
282    }
283
284    pub fn indexer_mut(&mut self) -> Result<&mut TestIndexer, RpcError> {
285        self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
286    }
287
288    pub fn test_accounts(&self) -> &TestAccounts {
289        &self.test_accounts
290    }
291
292    /// Get account pubkeys of one state Merkle tree.
293    pub fn get_state_merkle_tree_account(&self) -> StateMerkleTreeAccounts {
294        self.test_accounts.v1_state_trees[0]
295    }
296
297    pub fn get_address_merkle_tree(&self) -> AddressMerkleTreeAccounts {
298        self.test_accounts.v1_address_trees[0]
299    }
300
301    pub async fn add_indexer(
302        &mut self,
303        test_accounts: &TestAccounts,
304        batch_size: Option<usize>,
305    ) -> Result<(), RpcError> {
306        let indexer = TestIndexer::init_from_acounts(
307            &self.payer,
308            test_accounts,
309            batch_size.unwrap_or_default(),
310        )
311        .await;
312        self.indexer = Some(indexer);
313        Ok(())
314    }
315
316    pub fn clone_indexer(&self) -> Result<TestIndexer, RpcError> {
317        Ok((*self
318            .indexer
319            .as_ref()
320            .ok_or(RpcError::IndexerNotInitialized)?)
321        .clone())
322    }
323}
324
325impl MerkleTreeExt for LightProgramTest {}
326
327impl Debug for LightProgramTest {
328    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
329        f.debug_struct("LightProgramTest")
330            .field("context", &"ProgramTestContext")
331            .field("indexer", &self.indexer)
332            .field("test_accounts", &self.test_accounts)
333            .finish()
334    }
335}