bonfida_test_utils/
program_test_context_ext.rs

1use async_trait::async_trait;
2use solana_program::{
3    clock::Clock, instruction::Instruction, program_pack::Pack, pubkey::Pubkey, rent::Rent,
4    system_instruction::create_account,
5};
6use solana_program_test::ProgramTestContext;
7use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction};
8
9use crate::error::TestError;
10
11const NANOSECONDS_IN_SECOND: u128 = 1_000_000_000;
12
13#[async_trait]
14pub trait ProgramTestContextExt {
15    async fn mint_tokens(
16        &mut self,
17        mint_authority: &Keypair,
18        mint_pubkey: &Pubkey,
19        token_account: &Pubkey,
20        amount: u64,
21    ) -> Result<(), TestError>;
22
23    async fn get_token_account(
24        &mut self,
25        key: Pubkey,
26    ) -> Result<spl_token::state::Account, TestError>;
27
28    async fn sign_send_instructions(
29        &mut self,
30        instructions: &[Instruction],
31        signers: &[&Keypair],
32    ) -> Result<(), TestError>;
33
34    async fn warp_to_timestamp(&mut self, timestamp: i64) -> Result<(), TestError>;
35    async fn initialize_token_accounts(
36        &mut self,
37        mint: Pubkey,
38        owners: &[Pubkey],
39    ) -> Result<Vec<Pubkey>, TestError>;
40
41    async fn get_current_timestamp(&mut self) -> Result<i64, TestError>;
42
43    async fn initialize_new_account(
44        &mut self,
45        space: usize,
46        program_id: Pubkey,
47    ) -> Result<Pubkey, TestError>;
48}
49
50#[async_trait]
51impl ProgramTestContextExt for ProgramTestContext {
52    async fn mint_tokens(
53        &mut self,
54        mint_authority: &Keypair,
55        mint_pubkey: &Pubkey,
56        token_account: &Pubkey,
57        amount: u64,
58    ) -> Result<(), TestError> {
59        let mint_instruction = spl_token::instruction::mint_to(
60            &spl_token::ID,
61            mint_pubkey,
62            token_account,
63            &mint_authority.pubkey(),
64            &[],
65            amount,
66        )?;
67        self.sign_send_instructions(&[mint_instruction], &[mint_authority])
68            .await?;
69        Ok(())
70    }
71    async fn get_token_account(
72        &mut self,
73        key: Pubkey,
74    ) -> Result<spl_token::state::Account, TestError> {
75        let raw_account = self
76            .banks_client
77            .get_account(key)
78            .await?
79            .ok_or(TestError::AccountDoesNotExist)?;
80        if raw_account.owner != spl_token::ID {
81            return Err(TestError::InvalidTokenAccount);
82        }
83        Ok(spl_token::state::Account::unpack(&raw_account.data)?)
84    }
85    async fn sign_send_instructions(
86        &mut self,
87        instructions: &[Instruction],
88        signers: &[&Keypair],
89    ) -> Result<(), TestError> {
90        let mut transaction = Transaction::new_with_payer(instructions, Some(&self.payer.pubkey()));
91        let mut payer_signers = Vec::with_capacity(1 + signers.len());
92        payer_signers.push(&self.payer);
93        for s in signers {
94            payer_signers.push(s);
95        }
96        transaction.partial_sign(&payer_signers, self.last_blockhash);
97        self.banks_client.process_transaction(transaction).await?;
98        Ok(())
99    }
100
101    async fn warp_to_timestamp(&mut self, timestamp: i64) -> Result<(), TestError> {
102        let mut clock = self.banks_client.get_sysvar::<Clock>().await?;
103        if clock.unix_timestamp > timestamp {
104            return Err(TestError::InvalidTimestampForWarp);
105        }
106        let ns_per_slot = self.genesis_config().ns_per_slot();
107        let time_delta_ns =
108            (timestamp - clock.unix_timestamp).unsigned_abs() as u128 * NANOSECONDS_IN_SECOND;
109        let number_of_slots_to_warp: u64 = (time_delta_ns / ns_per_slot).try_into().unwrap();
110
111        clock.unix_timestamp = timestamp;
112        self.set_sysvar(&clock);
113        self.warp_to_slot(clock.slot + number_of_slots_to_warp)?;
114        Ok(())
115    }
116
117    async fn initialize_token_accounts(
118        &mut self,
119        mint: Pubkey,
120        owners: &[Pubkey],
121    ) -> Result<Vec<Pubkey>, TestError> {
122        let mut instructions = Vec::with_capacity(owners.len());
123        let mut account_keys = Vec::with_capacity(owners.len());
124        for o in owners {
125            let i = spl_associated_token_account::instruction::create_associated_token_account(
126                &self.payer.pubkey(),
127                o,
128                &mint,
129                &spl_token::ID,
130            );
131            account_keys.push(i.accounts[1].pubkey);
132            instructions.push(i);
133        }
134        for c in instructions.chunks(10) {
135            self.sign_send_instructions(c, &[]).await?;
136        }
137        Ok(account_keys)
138    }
139
140    async fn get_current_timestamp(&mut self) -> Result<i64, TestError> {
141        let clock = self.banks_client.get_sysvar::<Clock>().await?;
142        Ok(clock.unix_timestamp)
143    }
144
145    async fn initialize_new_account(
146        &mut self,
147        space: usize,
148        program_id: Pubkey,
149    ) -> Result<Pubkey, TestError> {
150        let account_keypair = Keypair::new();
151        let rent = self.banks_client.get_sysvar::<Rent>().await.unwrap();
152        let lamports = rent.minimum_balance(space);
153        let ix = create_account(
154            &self.payer.pubkey(),
155            &account_keypair.pubkey(),
156            lamports,
157            space as u64,
158            &program_id,
159        );
160        self.sign_send_instructions(&[ix], &[&account_keypair])
161            .await?;
162        Ok(account_keypair.pubkey())
163    }
164}