solana_budget_program/
budget_processor.rs

1//! budget program
2use crate::{
3    budget_expr::Witness,
4    budget_instruction::{BudgetError, BudgetInstruction},
5    budget_state::BudgetState,
6};
7use chrono::prelude::{DateTime, Utc};
8use log::*;
9use solana_sdk::{
10    account::{ReadableAccount, WritableAccount},
11    hash::hash,
12    instruction::InstructionError,
13    keyed_account::{keyed_account_at_index, KeyedAccount},
14    process_instruction::InvokeContext,
15    program_utils::limited_deserialize,
16    pubkey::Pubkey,
17};
18
19/// Process a Witness Signature. Any payment plans waiting on this signature
20/// will progress one step.
21fn apply_signature(
22    budget_state: &mut BudgetState,
23    witness_keyed_account: &KeyedAccount,
24    contract_keyed_account: &KeyedAccount,
25    to_keyed_account: Result<&KeyedAccount, InstructionError>,
26) -> Result<(), InstructionError> {
27    let mut final_payment = None;
28    if let Some(ref mut expr) = budget_state.pending_budget {
29        let key = witness_keyed_account.signer_key().unwrap();
30        expr.apply_witness(&Witness::Signature, key);
31        final_payment = expr.final_payment();
32    }
33
34    if let Some(payment) = final_payment {
35        if let Some(key) = witness_keyed_account.signer_key() {
36            if &payment.to == key {
37                budget_state.pending_budget = None;
38                contract_keyed_account
39                    .try_account_ref_mut()?
40                    .checked_sub_lamports(payment.lamports)?;
41                witness_keyed_account
42                    .try_account_ref_mut()?
43                    .checked_add_lamports(payment.lamports)?;
44                return Ok(());
45            }
46        }
47        let to_keyed_account = to_keyed_account?;
48        if &payment.to != to_keyed_account.unsigned_key() {
49            trace!("destination missing");
50            return Err(BudgetError::DestinationMissing.into());
51        }
52        budget_state.pending_budget = None;
53        contract_keyed_account
54            .try_account_ref_mut()?
55            .checked_sub_lamports(payment.lamports)?;
56        to_keyed_account
57            .try_account_ref_mut()?
58            .checked_add_lamports(payment.lamports)?;
59    }
60    Ok(())
61}
62
63/// Process a Witness Timestamp. Any payment plans waiting on this timestamp
64/// will progress one step.
65fn apply_timestamp(
66    budget_state: &mut BudgetState,
67    witness_keyed_account: &KeyedAccount,
68    contract_keyed_account: &KeyedAccount,
69    to_keyed_account: Result<&KeyedAccount, InstructionError>,
70    dt: DateTime<Utc>,
71) -> Result<(), InstructionError> {
72    // Check to see if any timelocked transactions can be completed.
73    let mut final_payment = None;
74
75    if let Some(ref mut expr) = budget_state.pending_budget {
76        let key = witness_keyed_account.signer_key().unwrap();
77        expr.apply_witness(&Witness::Timestamp(dt), key);
78        final_payment = expr.final_payment();
79    }
80
81    if let Some(payment) = final_payment {
82        let to_keyed_account = to_keyed_account?;
83        if &payment.to != to_keyed_account.unsigned_key() {
84            trace!("destination missing");
85            return Err(BudgetError::DestinationMissing.into());
86        }
87        budget_state.pending_budget = None;
88        contract_keyed_account
89            .try_account_ref_mut()?
90            .checked_sub_lamports(payment.lamports)?;
91        to_keyed_account
92            .try_account_ref_mut()?
93            .checked_add_lamports(payment.lamports)?;
94    }
95    Ok(())
96}
97
98/// Process an AccountData Witness and any payment waiting on it.
99fn apply_account_data(
100    budget_state: &mut BudgetState,
101    witness_keyed_account: &KeyedAccount,
102    contract_keyed_account: &KeyedAccount,
103    to_keyed_account: Result<&KeyedAccount, InstructionError>,
104) -> Result<(), InstructionError> {
105    // Check to see if any timelocked transactions can be completed.
106    let mut final_payment = None;
107
108    if let Some(ref mut expr) = budget_state.pending_budget {
109        let key = witness_keyed_account.unsigned_key();
110        let program_id = witness_keyed_account.owner()?;
111        let actual_hash = hash(&witness_keyed_account.try_account_ref()?.data());
112        expr.apply_witness(&Witness::AccountData(actual_hash, program_id), key);
113        final_payment = expr.final_payment();
114    }
115
116    if let Some(payment) = final_payment {
117        let to_keyed_account = to_keyed_account?;
118        if &payment.to != to_keyed_account.unsigned_key() {
119            trace!("destination missing");
120            return Err(BudgetError::DestinationMissing.into());
121        }
122        budget_state.pending_budget = None;
123        contract_keyed_account
124            .try_account_ref_mut()?
125            .checked_sub_lamports(payment.lamports)?;
126        to_keyed_account
127            .try_account_ref_mut()?
128            .checked_add_lamports(payment.lamports)?;
129    }
130    Ok(())
131}
132
133pub fn process_instruction(
134    _program_id: &Pubkey,
135    data: &[u8],
136    invoke_context: &mut dyn InvokeContext,
137) -> Result<(), InstructionError> {
138    let keyed_accounts = invoke_context.get_keyed_accounts()?;
139
140    let instruction = limited_deserialize(data)?;
141
142    trace!("process_instruction: {:?}", instruction);
143
144    match instruction {
145        BudgetInstruction::InitializeAccount(expr) => {
146            let contract_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
147
148            if let Some(payment) = expr.final_payment() {
149                let to_keyed_account = contract_keyed_account;
150                let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
151                contract_keyed_account
152                    .try_account_ref_mut()?
153                    .set_lamports(0);
154                to_keyed_account
155                    .try_account_ref_mut()?
156                    .checked_add_lamports(payment.lamports)?;
157                return Ok(());
158            }
159            let existing =
160                BudgetState::deserialize(&contract_keyed_account.try_account_ref_mut()?.data())
161                    .ok();
162            if Some(true) == existing.map(|x| x.initialized) {
163                trace!("contract already exists");
164                return Err(InstructionError::AccountAlreadyInitialized);
165            }
166            let budget_state = BudgetState {
167                pending_budget: Some(*expr),
168                initialized: true,
169            };
170            budget_state.serialize(
171                &mut contract_keyed_account
172                    .try_account_ref_mut()?
173                    .data_as_mut_slice(),
174            )
175        }
176        BudgetInstruction::ApplyTimestamp(dt) => {
177            let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
178            let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
179            let mut budget_state =
180                BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?;
181            if !budget_state.is_pending() {
182                return Ok(()); // Nothing to do here.
183            }
184            if !budget_state.initialized {
185                trace!("contract is uninitialized");
186                return Err(InstructionError::UninitializedAccount);
187            }
188            if witness_keyed_account.signer_key().is_none() {
189                return Err(InstructionError::MissingRequiredSignature);
190            }
191            trace!("apply timestamp");
192            apply_timestamp(
193                &mut budget_state,
194                witness_keyed_account,
195                contract_keyed_account,
196                keyed_account_at_index(keyed_accounts, 2),
197                dt,
198            )?;
199            trace!("apply timestamp committed");
200            budget_state.serialize(
201                &mut contract_keyed_account
202                    .try_account_ref_mut()?
203                    .data_as_mut_slice(),
204            )
205        }
206        BudgetInstruction::ApplySignature => {
207            let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
208            let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
209            let mut budget_state =
210                BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?;
211            if !budget_state.is_pending() {
212                return Ok(()); // Nothing to do here.
213            }
214            if !budget_state.initialized {
215                trace!("contract is uninitialized");
216                return Err(InstructionError::UninitializedAccount);
217            }
218            if witness_keyed_account.signer_key().is_none() {
219                return Err(InstructionError::MissingRequiredSignature);
220            }
221            trace!("apply signature");
222            apply_signature(
223                &mut budget_state,
224                witness_keyed_account,
225                contract_keyed_account,
226                keyed_account_at_index(keyed_accounts, 2),
227            )?;
228            trace!("apply signature committed");
229            budget_state.serialize(
230                &mut contract_keyed_account
231                    .try_account_ref_mut()?
232                    .data_as_mut_slice(),
233            )
234        }
235        BudgetInstruction::ApplyAccountData => {
236            let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
237            let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
238            let mut budget_state =
239                BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?;
240            if !budget_state.is_pending() {
241                return Ok(()); // Nothing to do here.
242            }
243            if !budget_state.initialized {
244                trace!("contract is uninitialized");
245                return Err(InstructionError::UninitializedAccount);
246            }
247            apply_account_data(
248                &mut budget_state,
249                witness_keyed_account,
250                contract_keyed_account,
251                keyed_account_at_index(keyed_accounts, 2),
252            )?;
253            trace!("apply account data committed");
254            budget_state.serialize(
255                &mut contract_keyed_account
256                    .try_account_ref_mut()?
257                    .data_as_mut_slice(),
258            )
259        }
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use crate::budget_instruction;
267    use crate::id;
268    use solana_runtime::bank::Bank;
269    use solana_runtime::bank_client::BankClient;
270    use solana_sdk::account::{Account, AccountSharedData};
271    use solana_sdk::client::SyncClient;
272    use solana_sdk::genesis_config::create_genesis_config;
273    use solana_sdk::hash::hash;
274    use solana_sdk::instruction::InstructionError;
275    use solana_sdk::message::Message;
276    use solana_sdk::signature::{Keypair, Signer};
277    use solana_sdk::transaction::TransactionError;
278
279    fn create_bank(lamports: u64) -> (Bank, Keypair) {
280        let (genesis_config, mint_keypair) = create_genesis_config(lamports);
281        let mut bank = Bank::new(&genesis_config);
282        bank.add_builtin("budget_program", id(), process_instruction);
283        (bank, mint_keypair)
284    }
285
286    #[test]
287    fn test_initialize_no_panic() {
288        let (bank, alice_keypair) = create_bank(1);
289        let bank_client = BankClient::new(bank);
290
291        let alice_pubkey = alice_keypair.pubkey();
292        let budget_keypair = Keypair::new();
293        let budget_pubkey = budget_keypair.pubkey();
294        let bob_pubkey = solana_sdk::pubkey::new_rand();
295
296        let mut instructions =
297            budget_instruction::payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 1);
298        instructions[1].accounts = vec![]; // <!-- Attack! Prevent accounts from being passed into processor.
299
300        let message = Message::new(&instructions, Some(&alice_pubkey));
301        assert_eq!(
302            bank_client
303                .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
304                .unwrap_err()
305                .unwrap(),
306            TransactionError::InstructionError(1, InstructionError::NotEnoughAccountKeys)
307        );
308    }
309
310    #[test]
311    fn test_budget_payment() {
312        let (bank, alice_keypair) = create_bank(10_000);
313        let bank_client = BankClient::new(bank);
314        let alice_pubkey = alice_keypair.pubkey();
315        let bob_pubkey = solana_sdk::pubkey::new_rand();
316        let budget_keypair = Keypair::new();
317        let budget_pubkey = budget_keypair.pubkey();
318        let instructions =
319            budget_instruction::payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 100);
320        let message = Message::new(&instructions, Some(&alice_pubkey));
321        bank_client
322            .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
323            .unwrap();
324        assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 100);
325    }
326
327    #[test]
328    fn test_unsigned_witness_key() {
329        let (bank, alice_keypair) = create_bank(10_000);
330        let bank_client = BankClient::new(bank);
331        let alice_pubkey = alice_keypair.pubkey();
332
333        // Initialize BudgetState
334        let budget_keypair = Keypair::new();
335        let budget_pubkey = budget_keypair.pubkey();
336        let bob_pubkey = solana_sdk::pubkey::new_rand();
337        let witness = solana_sdk::pubkey::new_rand();
338        let instructions = budget_instruction::when_signed(
339            &alice_pubkey,
340            &bob_pubkey,
341            &budget_pubkey,
342            &witness,
343            None,
344            1,
345        );
346        let message = Message::new(&instructions, Some(&alice_pubkey));
347        bank_client
348            .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
349            .unwrap();
350
351        // Attack! Part 1: Sign a witness transaction with a random key.
352        let mallory_keypair = Keypair::new();
353        let mallory_pubkey = mallory_keypair.pubkey();
354        bank_client
355            .transfer_and_confirm(1, &alice_keypair, &mallory_pubkey)
356            .unwrap();
357        let instruction =
358            budget_instruction::apply_signature(&mallory_pubkey, &budget_pubkey, &bob_pubkey);
359        let mut message = Message::new(&[instruction], Some(&mallory_pubkey));
360
361        // Attack! Part 2: Point the instruction to the expected, but unsigned, key.
362        message.account_keys.insert(3, alice_pubkey);
363        message.instructions[0].accounts[0] = 3;
364        message.instructions[0].program_id_index = 4;
365
366        // Ensure the transaction fails because of the unsigned key.
367        assert_eq!(
368            bank_client
369                .send_and_confirm_message(&[&mallory_keypair], message)
370                .unwrap_err()
371                .unwrap(),
372            TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
373        );
374    }
375
376    #[test]
377    fn test_unsigned_timestamp() {
378        let (bank, alice_keypair) = create_bank(10_000);
379        let bank_client = BankClient::new(bank);
380        let alice_pubkey = alice_keypair.pubkey();
381
382        // Initialize BudgetState
383        let budget_keypair = Keypair::new();
384        let budget_pubkey = budget_keypair.pubkey();
385        let bob_pubkey = solana_sdk::pubkey::new_rand();
386        let dt = Utc::now();
387        let instructions = budget_instruction::on_date(
388            &alice_pubkey,
389            &bob_pubkey,
390            &budget_pubkey,
391            dt,
392            &alice_pubkey,
393            None,
394            1,
395        );
396        let message = Message::new(&instructions, Some(&alice_pubkey));
397        bank_client
398            .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
399            .unwrap();
400
401        // Attack! Part 1: Sign a timestamp transaction with a random key.
402        let mallory_keypair = Keypair::new();
403        let mallory_pubkey = mallory_keypair.pubkey();
404        bank_client
405            .transfer_and_confirm(1, &alice_keypair, &mallory_pubkey)
406            .unwrap();
407        let instruction =
408            budget_instruction::apply_timestamp(&mallory_pubkey, &budget_pubkey, &bob_pubkey, dt);
409        let mut message = Message::new(&[instruction], Some(&mallory_pubkey));
410
411        // Attack! Part 2: Point the instruction to the expected, but unsigned, key.
412        message.account_keys.insert(3, alice_pubkey);
413        message.instructions[0].accounts[0] = 3;
414        message.instructions[0].program_id_index = 4;
415
416        // Ensure the transaction fails because of the unsigned key.
417        assert_eq!(
418            bank_client
419                .send_and_confirm_message(&[&mallory_keypair], message)
420                .unwrap_err()
421                .unwrap(),
422            TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
423        );
424    }
425
426    #[test]
427    fn test_pay_on_date() {
428        let (bank, alice_keypair) = create_bank(2);
429        let bank_client = BankClient::new(bank);
430        let alice_pubkey = alice_keypair.pubkey();
431        let budget_keypair = Keypair::new();
432        let budget_pubkey = budget_keypair.pubkey();
433        let bob_pubkey = solana_sdk::pubkey::new_rand();
434        let mallory_pubkey = solana_sdk::pubkey::new_rand();
435        let dt = Utc::now();
436
437        let instructions = budget_instruction::on_date(
438            &alice_pubkey,
439            &bob_pubkey,
440            &budget_pubkey,
441            dt,
442            &alice_pubkey,
443            None,
444            1,
445        );
446        let message = Message::new(&instructions, Some(&alice_pubkey));
447        bank_client
448            .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
449            .unwrap();
450        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
451        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
452
453        let contract_account = bank_client
454            .get_account_data(&budget_pubkey)
455            .unwrap()
456            .unwrap();
457        let budget_state = BudgetState::deserialize(&contract_account).unwrap();
458        assert!(budget_state.is_pending());
459
460        // Attack! Try to payout to mallory_pubkey
461        let instruction =
462            budget_instruction::apply_timestamp(&alice_pubkey, &budget_pubkey, &mallory_pubkey, dt);
463        assert_eq!(
464            bank_client
465                .send_and_confirm_instruction(&alice_keypair, instruction)
466                .unwrap_err()
467                .unwrap(),
468            TransactionError::InstructionError(
469                0,
470                InstructionError::Custom(BudgetError::DestinationMissing as u32)
471            )
472        );
473        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
474        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
475        assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 0);
476
477        let contract_account = bank_client
478            .get_account_data(&budget_pubkey)
479            .unwrap()
480            .unwrap();
481        let budget_state = BudgetState::deserialize(&contract_account).unwrap();
482        assert!(budget_state.is_pending());
483
484        // Now, acknowledge the time in the condition occurred and
485        // that pubkey's funds are now available.
486        let instruction =
487            budget_instruction::apply_timestamp(&alice_pubkey, &budget_pubkey, &bob_pubkey, dt);
488        bank_client
489            .send_and_confirm_instruction(&alice_keypair, instruction)
490            .unwrap();
491        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
492        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 0);
493        assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 1);
494        assert_eq!(bank_client.get_account_data(&budget_pubkey).unwrap(), None);
495    }
496
497    #[test]
498    fn test_cancel_payment() {
499        let (bank, alice_keypair) = create_bank(3);
500        let bank_client = BankClient::new(bank);
501        let alice_pubkey = alice_keypair.pubkey();
502        let budget_keypair = Keypair::new();
503        let budget_pubkey = budget_keypair.pubkey();
504        let bob_pubkey = solana_sdk::pubkey::new_rand();
505        let dt = Utc::now();
506
507        let instructions = budget_instruction::on_date(
508            &alice_pubkey,
509            &bob_pubkey,
510            &budget_pubkey,
511            dt,
512            &alice_pubkey,
513            Some(alice_pubkey),
514            1,
515        );
516        let message = Message::new(&instructions, Some(&alice_pubkey));
517        bank_client
518            .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
519            .unwrap();
520        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 2);
521        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
522
523        let contract_account = bank_client
524            .get_account_data(&budget_pubkey)
525            .unwrap()
526            .unwrap();
527        let budget_state = BudgetState::deserialize(&contract_account).unwrap();
528        assert!(budget_state.is_pending());
529
530        // Attack! try to put the lamports into the wrong account with cancel
531        let mallory_keypair = Keypair::new();
532        let mallory_pubkey = mallory_keypair.pubkey();
533        bank_client
534            .transfer_and_confirm(1, &alice_keypair, &mallory_pubkey)
535            .unwrap();
536        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
537
538        let instruction =
539            budget_instruction::apply_signature(&mallory_pubkey, &budget_pubkey, &bob_pubkey);
540        bank_client
541            .send_and_confirm_instruction(&mallory_keypair, instruction)
542            .unwrap();
543        // nothing should be changed because apply witness didn't finalize a payment
544        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
545        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
546        assert_eq!(bank_client.get_account_data(&bob_pubkey).unwrap(), None);
547
548        // Now, cancel the transaction. mint gets her funds back
549        let instruction =
550            budget_instruction::apply_signature(&alice_pubkey, &budget_pubkey, &alice_pubkey);
551        bank_client
552            .send_and_confirm_instruction(&alice_keypair, instruction)
553            .unwrap();
554        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 2);
555        assert_eq!(bank_client.get_account_data(&budget_pubkey).unwrap(), None);
556        assert_eq!(bank_client.get_account_data(&bob_pubkey).unwrap(), None);
557    }
558
559    #[test]
560    fn test_pay_when_account_data() {
561        let (bank, alice_keypair) = create_bank(42);
562        let game_pubkey = solana_sdk::pubkey::new_rand();
563        let game_account = AccountSharedData::from(Account {
564            lamports: 1,
565            data: vec![1, 2, 3],
566            ..Account::default()
567        });
568        bank.store_account(&game_pubkey, &game_account);
569        assert_eq!(
570            bank.get_account(&game_pubkey).unwrap().data(),
571            &vec![1, 2, 3]
572        );
573
574        let bank_client = BankClient::new(bank);
575
576        let alice_pubkey = alice_keypair.pubkey();
577        let game_hash = hash(&[1, 2, 3]);
578        let budget_keypair = Keypair::new();
579        let budget_pubkey = budget_keypair.pubkey();
580        let bob_keypair = Keypair::new();
581        let bob_pubkey = bob_keypair.pubkey();
582
583        // Give Bob some lamports so he can sign the witness transaction.
584        bank_client
585            .transfer_and_confirm(1, &alice_keypair, &bob_pubkey)
586            .unwrap();
587
588        let instructions = budget_instruction::when_account_data(
589            &alice_pubkey,
590            &bob_pubkey,
591            &budget_pubkey,
592            &game_pubkey,
593            game_account.owner(),
594            game_hash,
595            41,
596        );
597        let message = Message::new(&instructions, Some(&alice_pubkey));
598        bank_client
599            .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
600            .unwrap();
601        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 0);
602        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 41);
603
604        let contract_account = bank_client
605            .get_account_data(&budget_pubkey)
606            .unwrap()
607            .unwrap();
608        let budget_state = BudgetState::deserialize(&contract_account).unwrap();
609        assert!(budget_state.is_pending());
610
611        // Acknowledge the condition occurred and that Bob's funds are now available.
612        let instruction =
613            budget_instruction::apply_account_data(&game_pubkey, &budget_pubkey, &bob_pubkey);
614
615        // Anyone can sign the message, but presumably it's Bob, since he's the
616        // one claiming the payout.
617        let message = Message::new(&[instruction], Some(&bob_pubkey));
618        bank_client
619            .send_and_confirm_message(&[&bob_keypair], message)
620            .unwrap();
621
622        assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 0);
623        assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 0);
624        assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 42);
625        assert_eq!(bank_client.get_account_data(&budget_pubkey).unwrap(), None);
626    }
627}