light_test_utils/rpc/
test_rpc.rs

1use std::fmt::{Debug, Formatter};
2
3use anchor_lang::prelude::Pubkey;
4use anchor_lang::solana_program::clock::Slot;
5use anchor_lang::solana_program::hash::Hash;
6use anchor_lang::solana_program::system_instruction;
7use anchor_lang::AnchorDeserialize;
8use async_trait::async_trait;
9use solana_program_test::{BanksClientError, ProgramTestContext};
10use solana_sdk::account::{Account, AccountSharedData};
11use solana_sdk::commitment_config::CommitmentConfig;
12use solana_sdk::epoch_info::EpochInfo;
13use solana_sdk::instruction::{Instruction, InstructionError};
14use solana_sdk::signature::{Keypair, Signature};
15use solana_sdk::signer::Signer;
16use solana_sdk::transaction::{Transaction, TransactionError};
17
18use light_client::rpc::errors::RpcError;
19use light_client::rpc::RpcConnection;
20use light_client::transaction_params::TransactionParams;
21
22pub struct ProgramTestRpcConnection {
23    pub context: ProgramTestContext,
24}
25
26impl Debug for ProgramTestRpcConnection {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        write!(f, "ProgramTestRpcConnection")
29    }
30}
31
32#[async_trait]
33impl RpcConnection for ProgramTestRpcConnection {
34    fn new<U: ToString>(_url: U, _commitment_config: Option<CommitmentConfig>) -> Self
35    where
36        Self: Sized,
37    {
38        unimplemented!()
39    }
40
41    fn get_payer(&self) -> &Keypair {
42        &self.context.payer
43    }
44
45    fn get_url(&self) -> String {
46        unimplemented!("get_url doesn't make sense for ProgramTestRpcConnection")
47    }
48
49    async fn health(&self) -> Result<(), RpcError> {
50        unimplemented!()
51    }
52
53    async fn get_block_time(&self, _slot: u64) -> Result<i64, RpcError> {
54        unimplemented!()
55    }
56
57    async fn get_epoch_info(&self) -> Result<EpochInfo, RpcError> {
58        unimplemented!()
59    }
60
61    async fn get_program_accounts(
62        &self,
63        _program_id: &Pubkey,
64    ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
65        unimplemented!("get_program_accounts")
66    }
67
68    async fn process_transaction(
69        &mut self,
70        transaction: Transaction,
71    ) -> Result<Signature, RpcError> {
72        let sig = *transaction.signatures.first().unwrap();
73        let result = self
74            .context
75            .banks_client
76            .process_transaction_with_metadata(transaction)
77            .await
78            .map_err(RpcError::from)?;
79        result.result.map_err(RpcError::TransactionError)?;
80        Ok(sig)
81    }
82
83    async fn process_transaction_with_context(
84        &mut self,
85        transaction: Transaction,
86    ) -> Result<(Signature, Slot), RpcError> {
87        let sig = *transaction.signatures.first().unwrap();
88        let result = self
89            .context
90            .banks_client
91            .process_transaction_with_metadata(transaction)
92            .await
93            .map_err(RpcError::from)?;
94        result.result.map_err(RpcError::TransactionError)?;
95        let slot = self.context.banks_client.get_root_slot().await?;
96        Ok((sig, slot))
97    }
98
99    async fn create_and_send_transaction_with_event<T>(
100        &mut self,
101        instruction: &[Instruction],
102        payer: &Pubkey,
103        signers: &[&Keypair],
104        transaction_params: Option<TransactionParams>,
105    ) -> Result<Option<(T, Signature, Slot)>, RpcError>
106    where
107        T: AnchorDeserialize + Send + Debug,
108    {
109        let pre_balance = self
110            .context
111            .banks_client
112            .get_account(*payer)
113            .await?
114            .unwrap()
115            .lamports;
116
117        let transaction = Transaction::new_signed_with_payer(
118            instruction,
119            Some(payer),
120            signers,
121            self.context.get_new_latest_blockhash().await?,
122        );
123
124        let signature = transaction.signatures[0];
125        // Simulate the transaction. Currently, in banks-client/server, only
126        // simulations are able to track CPIs. Therefore, simulating is the
127        // only way to retrieve the event.
128        let simulation_result = self
129            .context
130            .banks_client
131            .simulate_transaction(transaction.clone())
132            .await?;
133        // Handle an error nested in the simulation result.
134        if let Some(Err(e)) = simulation_result.result {
135            let error = match e {
136                TransactionError::InstructionError(_, _) => RpcError::TransactionError(e),
137                _ => RpcError::from(BanksClientError::TransactionError(e)),
138            };
139            return Err(error);
140        }
141
142        // Retrieve the event.
143        let event = simulation_result
144            .simulation_details
145            .and_then(|details| details.inner_instructions)
146            .and_then(|instructions| {
147                instructions.iter().flatten().find_map(|inner_instruction| {
148                    T::try_from_slice(inner_instruction.instruction.data.as_slice()).ok()
149                })
150            });
151        // If transaction was successful, execute it.
152        if let Some(Ok(())) = simulation_result.result {
153            let result = self
154                .context
155                .banks_client
156                .process_transaction(transaction)
157                .await;
158            if let Err(e) = result {
159                let error = RpcError::from(e);
160                return Err(error);
161            }
162        }
163
164        // assert correct rollover fee and network_fee distribution
165        if let Some(transaction_params) = transaction_params {
166            let mut deduped_signers = signers.to_vec();
167            deduped_signers.dedup();
168            let post_balance = self.get_account(*payer).await?.unwrap().lamports;
169
170            // a network_fee is charged if there are input compressed accounts or new addresses
171            let mut network_fee: i64 = 0;
172            if transaction_params.num_input_compressed_accounts != 0 {
173                network_fee += transaction_params.fee_config.network_fee as i64;
174            }
175            if transaction_params.num_new_addresses != 0 {
176                network_fee += transaction_params.fee_config.address_network_fee as i64;
177            }
178            let expected_post_balance = pre_balance as i64
179                - i64::from(transaction_params.num_new_addresses)
180                    * transaction_params.fee_config.address_queue_rollover as i64
181                - i64::from(transaction_params.num_output_compressed_accounts)
182                    * transaction_params.fee_config.state_merkle_tree_rollover as i64
183                - transaction_params.compress
184                - transaction_params.fee_config.solana_network_fee * deduped_signers.len() as i64
185                - network_fee;
186
187            if post_balance as i64 != expected_post_balance {
188                println!("transaction_params: {:?}", transaction_params);
189                println!("pre_balance: {}", pre_balance);
190                println!("post_balance: {}", post_balance);
191                println!("expected post_balance: {}", expected_post_balance);
192                println!(
193                    "diff post_balance: {}",
194                    post_balance as i64 - expected_post_balance
195                );
196                println!(
197                    "rollover fee: {}",
198                    transaction_params.fee_config.state_merkle_tree_rollover
199                );
200                println!(
201                    "address_network_fee: {}",
202                    transaction_params.fee_config.address_network_fee
203                );
204                println!("network_fee: {}", network_fee);
205                println!("num signers {}", deduped_signers.len());
206                return Err(RpcError::from(BanksClientError::TransactionError(
207                    TransactionError::InstructionError(0, InstructionError::Custom(11111)),
208                )));
209            }
210        }
211
212        let slot = self.context.banks_client.get_root_slot().await?;
213        let result = event.map(|event| (event, signature, slot));
214        Ok(result)
215    }
216
217    async fn confirm_transaction(&self, _transaction: Signature) -> Result<bool, RpcError> {
218        Ok(true)
219    }
220
221    async fn get_account(&mut self, address: Pubkey) -> Result<Option<Account>, RpcError> {
222        self.context
223            .banks_client
224            .get_account(address)
225            .await
226            .map_err(RpcError::from)
227    }
228
229    fn set_account(&mut self, address: &Pubkey, account: &AccountSharedData) {
230        self.context.set_account(address, account);
231    }
232
233    async fn get_minimum_balance_for_rent_exemption(
234        &mut self,
235        data_len: usize,
236    ) -> Result<u64, RpcError> {
237        let rent = self
238            .context
239            .banks_client
240            .get_rent()
241            .await
242            .map_err(RpcError::from);
243
244        Ok(rent?.minimum_balance(data_len))
245    }
246
247    async fn airdrop_lamports(
248        &mut self,
249        to: &Pubkey,
250        lamports: u64,
251    ) -> Result<Signature, RpcError> {
252        // Create a transfer instruction
253        let transfer_instruction =
254            system_instruction::transfer(&self.context.payer.pubkey(), to, lamports);
255        let latest_blockhash = self.get_latest_blockhash().await.unwrap();
256        // Create and sign a transaction
257        let transaction = Transaction::new_signed_with_payer(
258            &[transfer_instruction],
259            Some(&self.get_payer().pubkey()),
260            &vec![&self.get_payer()],
261            latest_blockhash,
262        );
263        let sig = *transaction.signatures.first().unwrap();
264
265        // Send the transaction
266        self.context
267            .banks_client
268            .process_transaction(transaction)
269            .await?;
270
271        Ok(sig)
272    }
273
274    async fn get_balance(&mut self, pubkey: &Pubkey) -> Result<u64, RpcError> {
275        self.context
276            .banks_client
277            .get_balance(*pubkey)
278            .await
279            .map_err(RpcError::from)
280    }
281
282    async fn get_latest_blockhash(&mut self) -> Result<Hash, RpcError> {
283        self.context
284            .get_new_latest_blockhash()
285            .await
286            .map_err(|e| RpcError::from(BanksClientError::from(e)))
287    }
288
289    async fn get_slot(&mut self) -> Result<u64, RpcError> {
290        self.context
291            .banks_client
292            .get_root_slot()
293            .await
294            .map_err(RpcError::from)
295    }
296
297    async fn warp_to_slot(&mut self, slot: Slot) -> Result<(), RpcError> {
298        self.context
299            .warp_to_slot(slot)
300            .map_err(|_| RpcError::InvalidWarpSlot)
301    }
302
303    async fn send_transaction(&self, _transaction: &Transaction) -> Result<Signature, RpcError> {
304        unimplemented!("send transaction is unimplemented for ProgramTestRpcConnection")
305    }
306}