solana_svm/
program_loader.rs

1use {
2    solana_account::{state_traits::StateMut, AccountSharedData, ReadableAccount},
3    solana_clock::Slot,
4    solana_instruction::error::InstructionError,
5    solana_loader_v3_interface::state::UpgradeableLoaderState,
6    solana_loader_v4_interface::state::{LoaderV4State, LoaderV4Status},
7    solana_program_runtime::loaded_programs::{
8        LoadProgramMetrics, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
9        ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
10    },
11    solana_pubkey::Pubkey,
12    solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4},
13    solana_svm_callback::TransactionProcessingCallback,
14    solana_timings::ExecuteTimings,
15    solana_transaction_error::{TransactionError, TransactionResult},
16    solana_type_overrides::sync::Arc,
17};
18
19#[derive(Debug)]
20pub(crate) enum ProgramAccountLoadResult {
21    InvalidAccountData(ProgramCacheEntryOwner),
22    ProgramOfLoaderV1(AccountSharedData),
23    ProgramOfLoaderV2(AccountSharedData),
24    ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot),
25    ProgramOfLoaderV4(AccountSharedData, Slot),
26}
27
28pub(crate) fn load_program_from_bytes(
29    load_program_metrics: &mut LoadProgramMetrics,
30    programdata: &[u8],
31    loader_key: &Pubkey,
32    account_size: usize,
33    deployment_slot: Slot,
34    program_runtime_environment: ProgramRuntimeEnvironment,
35    reloading: bool,
36) -> std::result::Result<ProgramCacheEntry, Box<dyn std::error::Error>> {
37    if reloading {
38        // Safety: this is safe because the program is being reloaded in the cache.
39        unsafe {
40            ProgramCacheEntry::reload(
41                loader_key,
42                program_runtime_environment.clone(),
43                deployment_slot,
44                deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
45                programdata,
46                account_size,
47                load_program_metrics,
48            )
49        }
50    } else {
51        ProgramCacheEntry::new(
52            loader_key,
53            program_runtime_environment.clone(),
54            deployment_slot,
55            deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
56            programdata,
57            account_size,
58            load_program_metrics,
59        )
60    }
61}
62
63pub(crate) fn load_program_accounts<CB: TransactionProcessingCallback>(
64    callbacks: &CB,
65    pubkey: &Pubkey,
66) -> Option<ProgramAccountLoadResult> {
67    let program_account = callbacks.get_account_shared_data(pubkey)?;
68
69    if loader_v4::check_id(program_account.owner()) {
70        return Some(
71            solana_loader_v4_program::get_state(program_account.data())
72                .ok()
73                .and_then(|state| {
74                    (!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot)
75                })
76                .map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot))
77                .unwrap_or(ProgramAccountLoadResult::InvalidAccountData(
78                    ProgramCacheEntryOwner::LoaderV4,
79                )),
80        );
81    }
82
83    if bpf_loader_deprecated::check_id(program_account.owner()) {
84        return Some(ProgramAccountLoadResult::ProgramOfLoaderV1(program_account));
85    }
86
87    if bpf_loader::check_id(program_account.owner()) {
88        return Some(ProgramAccountLoadResult::ProgramOfLoaderV2(program_account));
89    }
90
91    if let Ok(UpgradeableLoaderState::Program {
92        programdata_address,
93    }) = program_account.state()
94    {
95        if let Some(programdata_account) = callbacks.get_account_shared_data(&programdata_address) {
96            if let Ok(UpgradeableLoaderState::ProgramData {
97                slot,
98                upgrade_authority_address: _,
99            }) = programdata_account.state()
100            {
101                return Some(ProgramAccountLoadResult::ProgramOfLoaderV3(
102                    program_account,
103                    programdata_account,
104                    slot,
105                ));
106            }
107        }
108    }
109    Some(ProgramAccountLoadResult::InvalidAccountData(
110        ProgramCacheEntryOwner::LoaderV3,
111    ))
112}
113
114/// Loads the program with the given pubkey.
115///
116/// If the account doesn't exist it returns `None`. If the account does exist, it must be a program
117/// account (belong to one of the program loaders). Returns `Some(InvalidAccountData)` if the program
118/// account is `Closed`, contains invalid data or any of the programdata accounts are invalid.
119pub fn load_program_with_pubkey<CB: TransactionProcessingCallback>(
120    callbacks: &CB,
121    environments: &ProgramRuntimeEnvironments,
122    pubkey: &Pubkey,
123    slot: Slot,
124    execute_timings: &mut ExecuteTimings,
125    reload: bool,
126) -> Option<Arc<ProgramCacheEntry>> {
127    let mut load_program_metrics = LoadProgramMetrics {
128        program_id: pubkey.to_string(),
129        ..LoadProgramMetrics::default()
130    };
131
132    let loaded_program = match load_program_accounts(callbacks, pubkey)? {
133        ProgramAccountLoadResult::InvalidAccountData(owner) => Ok(
134            ProgramCacheEntry::new_tombstone(slot, owner, ProgramCacheEntryType::Closed),
135        ),
136
137        ProgramAccountLoadResult::ProgramOfLoaderV1(program_account) => load_program_from_bytes(
138            &mut load_program_metrics,
139            program_account.data(),
140            program_account.owner(),
141            program_account.data().len(),
142            0,
143            environments.program_runtime_v1.clone(),
144            reload,
145        )
146        .map_err(|_| (0, ProgramCacheEntryOwner::LoaderV1)),
147
148        ProgramAccountLoadResult::ProgramOfLoaderV2(program_account) => load_program_from_bytes(
149            &mut load_program_metrics,
150            program_account.data(),
151            program_account.owner(),
152            program_account.data().len(),
153            0,
154            environments.program_runtime_v1.clone(),
155            reload,
156        )
157        .map_err(|_| (0, ProgramCacheEntryOwner::LoaderV2)),
158
159        ProgramAccountLoadResult::ProgramOfLoaderV3(program_account, programdata_account, slot) => {
160            programdata_account
161                .data()
162                .get(UpgradeableLoaderState::size_of_programdata_metadata()..)
163                .ok_or(Box::new(InstructionError::InvalidAccountData).into())
164                .and_then(|programdata| {
165                    load_program_from_bytes(
166                        &mut load_program_metrics,
167                        programdata,
168                        program_account.owner(),
169                        program_account
170                            .data()
171                            .len()
172                            .saturating_add(programdata_account.data().len()),
173                        slot,
174                        environments.program_runtime_v1.clone(),
175                        reload,
176                    )
177                })
178                .map_err(|_| (slot, ProgramCacheEntryOwner::LoaderV3))
179        }
180
181        ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => program_account
182            .data()
183            .get(LoaderV4State::program_data_offset()..)
184            .ok_or(Box::new(InstructionError::InvalidAccountData).into())
185            .and_then(|elf_bytes| {
186                load_program_from_bytes(
187                    &mut load_program_metrics,
188                    elf_bytes,
189                    &loader_v4::id(),
190                    program_account.data().len(),
191                    slot,
192                    environments.program_runtime_v1.clone(),
193                    reload,
194                )
195            })
196            .map_err(|_| (slot, ProgramCacheEntryOwner::LoaderV4)),
197    }
198    .unwrap_or_else(|(slot, owner)| {
199        let env = environments.program_runtime_v1.clone();
200        ProgramCacheEntry::new_tombstone(
201            slot,
202            owner,
203            ProgramCacheEntryType::FailedVerification(env),
204        )
205    });
206
207    load_program_metrics.submit_datapoint(&mut execute_timings.details);
208    loaded_program.update_access_slot(slot);
209    Some(Arc::new(loaded_program))
210}
211
212/// Find the slot in which the program was most recently modified.
213/// Returns slot 0 for programs deployed with v1/v2 loaders, since programs deployed
214/// with those loaders do not retain deployment slot information.
215/// Returns an error if the program's account state can not be found or parsed.
216pub(crate) fn get_program_modification_slot<CB: TransactionProcessingCallback>(
217    callbacks: &CB,
218    pubkey: &Pubkey,
219) -> TransactionResult<Slot> {
220    let program = callbacks
221        .get_account_shared_data(pubkey)
222        .ok_or(TransactionError::ProgramAccountNotFound)?;
223    if bpf_loader_upgradeable::check_id(program.owner()) {
224        if let Ok(UpgradeableLoaderState::Program {
225            programdata_address,
226        }) = program.state()
227        {
228            let programdata = callbacks
229                .get_account_shared_data(&programdata_address)
230                .ok_or(TransactionError::ProgramAccountNotFound)?;
231            if let Ok(UpgradeableLoaderState::ProgramData {
232                slot,
233                upgrade_authority_address: _,
234            }) = programdata.state()
235            {
236                return Ok(slot);
237            }
238        }
239        Err(TransactionError::ProgramAccountNotFound)
240    } else if loader_v4::check_id(program.owner()) {
241        let state = solana_loader_v4_program::get_state(program.data())
242            .map_err(|_| TransactionError::ProgramAccountNotFound)?;
243        Ok(state.slot)
244    } else {
245        Ok(0)
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use {
252        super::*,
253        crate::transaction_processor::TransactionBatchProcessor,
254        solana_account::WritableAccount,
255        solana_program_runtime::{
256            loaded_programs::{BlockRelation, ForkGraph, ProgramRuntimeEnvironments},
257            solana_sbpf::program::BuiltinProgram,
258        },
259        solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable},
260        solana_svm_callback::InvokeContextCallback,
261        std::{
262            cell::RefCell,
263            collections::HashMap,
264            env,
265            fs::{self, File},
266            io::Read,
267        },
268    };
269
270    struct TestForkGraph {}
271
272    impl ForkGraph for TestForkGraph {
273        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
274            BlockRelation::Unknown
275        }
276    }
277
278    #[derive(Default, Clone)]
279    pub(crate) struct MockBankCallback {
280        pub(crate) account_shared_data: RefCell<HashMap<Pubkey, AccountSharedData>>,
281    }
282
283    impl InvokeContextCallback for MockBankCallback {}
284
285    impl TransactionProcessingCallback for MockBankCallback {
286        fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option<usize> {
287            if let Some(data) = self.account_shared_data.borrow().get(account) {
288                if data.lamports() == 0 {
289                    None
290                } else {
291                    owners.iter().position(|entry| data.owner() == entry)
292                }
293            } else {
294                None
295            }
296        }
297
298        fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
299            self.account_shared_data.borrow().get(pubkey).cloned()
300        }
301
302        fn add_builtin_account(&self, name: &str, program_id: &Pubkey) {
303            let mut account_data = AccountSharedData::default();
304            account_data.set_data(name.as_bytes().to_vec());
305            self.account_shared_data
306                .borrow_mut()
307                .insert(*program_id, account_data);
308        }
309    }
310
311    #[test]
312    fn test_load_program_accounts_account_not_found() {
313        let mock_bank = MockBankCallback::default();
314        let key = Pubkey::new_unique();
315
316        let result = load_program_accounts(&mock_bank, &key);
317        assert!(result.is_none());
318
319        let mut account_data = AccountSharedData::default();
320        account_data.set_owner(bpf_loader_upgradeable::id());
321        let state = UpgradeableLoaderState::Program {
322            programdata_address: Pubkey::new_unique(),
323        };
324        account_data.set_data(bincode::serialize(&state).unwrap());
325        mock_bank
326            .account_shared_data
327            .borrow_mut()
328            .insert(key, account_data.clone());
329
330        let result = load_program_accounts(&mock_bank, &key);
331        assert!(matches!(
332            result,
333            Some(ProgramAccountLoadResult::InvalidAccountData(_))
334        ));
335
336        account_data.set_data(Vec::new());
337        mock_bank
338            .account_shared_data
339            .borrow_mut()
340            .insert(key, account_data);
341
342        let result = load_program_accounts(&mock_bank, &key);
343
344        assert!(matches!(
345            result,
346            Some(ProgramAccountLoadResult::InvalidAccountData(_))
347        ));
348    }
349
350    #[test]
351    fn test_load_program_accounts_loader_v4() {
352        let key = Pubkey::new_unique();
353        let mock_bank = MockBankCallback::default();
354        let mut account_data = AccountSharedData::default();
355        account_data.set_owner(loader_v4::id());
356        mock_bank
357            .account_shared_data
358            .borrow_mut()
359            .insert(key, account_data.clone());
360
361        let result = load_program_accounts(&mock_bank, &key);
362        assert!(matches!(
363            result,
364            Some(ProgramAccountLoadResult::InvalidAccountData(_))
365        ));
366
367        account_data.set_data(vec![0; 64]);
368        mock_bank
369            .account_shared_data
370            .borrow_mut()
371            .insert(key, account_data.clone());
372        let result = load_program_accounts(&mock_bank, &key);
373        assert!(matches!(
374            result,
375            Some(ProgramAccountLoadResult::InvalidAccountData(_))
376        ));
377
378        let loader_data = LoaderV4State {
379            slot: 25,
380            authority_address_or_next_version: Pubkey::new_unique(),
381            status: LoaderV4Status::Deployed,
382        };
383        let encoded = unsafe {
384            std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>(
385                &loader_data,
386            )
387        };
388        account_data.set_data(encoded.to_vec());
389        mock_bank
390            .account_shared_data
391            .borrow_mut()
392            .insert(key, account_data.clone());
393
394        let result = load_program_accounts(&mock_bank, &key);
395
396        match result {
397            Some(ProgramAccountLoadResult::ProgramOfLoaderV4(data, slot)) => {
398                assert_eq!(data, account_data);
399                assert_eq!(slot, 25);
400            }
401
402            _ => panic!("Invalid result"),
403        }
404    }
405
406    #[test]
407    fn test_load_program_accounts_loader_v1_or_v2() {
408        let key = Pubkey::new_unique();
409        let mock_bank = MockBankCallback::default();
410        let mut account_data = AccountSharedData::default();
411        account_data.set_owner(bpf_loader::id());
412        mock_bank
413            .account_shared_data
414            .borrow_mut()
415            .insert(key, account_data.clone());
416
417        let result = load_program_accounts(&mock_bank, &key);
418        match result {
419            Some(ProgramAccountLoadResult::ProgramOfLoaderV1(data))
420            | Some(ProgramAccountLoadResult::ProgramOfLoaderV2(data)) => {
421                assert_eq!(data, account_data);
422            }
423            _ => panic!("Invalid result"),
424        }
425    }
426
427    #[test]
428    fn test_load_program_accounts_success() {
429        let key1 = Pubkey::new_unique();
430        let key2 = Pubkey::new_unique();
431        let mock_bank = MockBankCallback::default();
432
433        let mut account_data = AccountSharedData::default();
434        account_data.set_owner(bpf_loader_upgradeable::id());
435
436        let state = UpgradeableLoaderState::Program {
437            programdata_address: key2,
438        };
439        account_data.set_data(bincode::serialize(&state).unwrap());
440        mock_bank
441            .account_shared_data
442            .borrow_mut()
443            .insert(key1, account_data.clone());
444
445        let state = UpgradeableLoaderState::ProgramData {
446            slot: 25,
447            upgrade_authority_address: None,
448        };
449        let mut account_data2 = AccountSharedData::default();
450        account_data2.set_data(bincode::serialize(&state).unwrap());
451        mock_bank
452            .account_shared_data
453            .borrow_mut()
454            .insert(key2, account_data2.clone());
455
456        let result = load_program_accounts(&mock_bank, &key1);
457
458        match result {
459            Some(ProgramAccountLoadResult::ProgramOfLoaderV3(data1, data2, slot)) => {
460                assert_eq!(data1, account_data);
461                assert_eq!(data2, account_data2);
462                assert_eq!(slot, 25);
463            }
464
465            _ => panic!("Invalid result"),
466        }
467    }
468
469    fn load_test_program() -> Vec<u8> {
470        let mut dir = env::current_dir().unwrap();
471        dir.push("tests");
472        dir.push("example-programs");
473        dir.push("hello-solana");
474        dir.push("hello_solana_program.so");
475        let mut file = File::open(dir.clone()).expect("file not found");
476        let metadata = fs::metadata(dir).expect("Unable to read metadata");
477        let mut buffer = vec![0; metadata.len() as usize];
478        file.read_exact(&mut buffer).expect("Buffer overflow");
479        buffer
480    }
481
482    #[test]
483    fn test_load_program_from_bytes() {
484        let buffer = load_test_program();
485
486        let mut metrics = LoadProgramMetrics::default();
487        let loader = bpf_loader_upgradeable::id();
488        let size = buffer.len();
489        let slot = 2;
490        let environment = ProgramRuntimeEnvironment::new(BuiltinProgram::new_mock());
491
492        let result = load_program_from_bytes(
493            &mut metrics,
494            &buffer,
495            &loader,
496            size,
497            slot,
498            environment.clone(),
499            false,
500        );
501
502        assert!(result.is_ok());
503
504        let result = load_program_from_bytes(
505            &mut metrics,
506            &buffer,
507            &loader,
508            size,
509            slot,
510            environment,
511            true,
512        );
513
514        assert!(result.is_ok());
515    }
516
517    #[test]
518    fn test_load_program_not_found() {
519        let mock_bank = MockBankCallback::default();
520        let key = Pubkey::new_unique();
521        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
522
523        let result = load_program_with_pubkey(
524            &mock_bank,
525            &batch_processor.get_environments_for_epoch(50).unwrap(),
526            &key,
527            500,
528            &mut ExecuteTimings::default(),
529            false,
530        );
531        assert!(result.is_none());
532    }
533
534    #[test]
535    fn test_load_program_invalid_account_data() {
536        let key = Pubkey::new_unique();
537        let mock_bank = MockBankCallback::default();
538        let mut account_data = AccountSharedData::default();
539        account_data.set_owner(loader_v4::id());
540        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
541        mock_bank
542            .account_shared_data
543            .borrow_mut()
544            .insert(key, account_data.clone());
545
546        let result = load_program_with_pubkey(
547            &mock_bank,
548            &batch_processor.get_environments_for_epoch(20).unwrap(),
549            &key,
550            0, // Slot 0
551            &mut ExecuteTimings::default(),
552            false,
553        );
554
555        let loaded_program = ProgramCacheEntry::new_tombstone(
556            0, // Slot 0
557            ProgramCacheEntryOwner::LoaderV4,
558            ProgramCacheEntryType::FailedVerification(
559                batch_processor
560                    .get_environments_for_epoch(20)
561                    .unwrap()
562                    .program_runtime_v1,
563            ),
564        );
565        assert_eq!(result.unwrap(), Arc::new(loaded_program));
566    }
567
568    #[test]
569    fn test_load_program_program_loader_v1_or_v2() {
570        let key = Pubkey::new_unique();
571        let mock_bank = MockBankCallback::default();
572        let mut account_data = AccountSharedData::default();
573        account_data.set_owner(bpf_loader::id());
574        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
575        mock_bank
576            .account_shared_data
577            .borrow_mut()
578            .insert(key, account_data.clone());
579
580        // This should return an error
581        let result = load_program_with_pubkey(
582            &mock_bank,
583            &batch_processor.get_environments_for_epoch(20).unwrap(),
584            &key,
585            200,
586            &mut ExecuteTimings::default(),
587            false,
588        );
589        let loaded_program = ProgramCacheEntry::new_tombstone(
590            0,
591            ProgramCacheEntryOwner::LoaderV2,
592            ProgramCacheEntryType::FailedVerification(
593                batch_processor
594                    .get_environments_for_epoch(20)
595                    .unwrap()
596                    .program_runtime_v1,
597            ),
598        );
599        assert_eq!(result.unwrap(), Arc::new(loaded_program));
600
601        let buffer = load_test_program();
602        account_data.set_data(buffer);
603
604        mock_bank
605            .account_shared_data
606            .borrow_mut()
607            .insert(key, account_data.clone());
608
609        let result = load_program_with_pubkey(
610            &mock_bank,
611            &batch_processor.get_environments_for_epoch(20).unwrap(),
612            &key,
613            200,
614            &mut ExecuteTimings::default(),
615            false,
616        );
617
618        let environments = ProgramRuntimeEnvironments::default();
619        let expected = load_program_from_bytes(
620            &mut LoadProgramMetrics::default(),
621            account_data.data(),
622            account_data.owner(),
623            account_data.data().len(),
624            0,
625            environments.program_runtime_v1.clone(),
626            false,
627        );
628
629        assert_eq!(result.unwrap(), Arc::new(expected.unwrap()));
630    }
631
632    #[test]
633    fn test_load_program_program_loader_v3() {
634        let key1 = Pubkey::new_unique();
635        let key2 = Pubkey::new_unique();
636        let mock_bank = MockBankCallback::default();
637        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
638
639        let mut account_data = AccountSharedData::default();
640        account_data.set_owner(bpf_loader_upgradeable::id());
641
642        let state = UpgradeableLoaderState::Program {
643            programdata_address: key2,
644        };
645        account_data.set_data(bincode::serialize(&state).unwrap());
646        mock_bank
647            .account_shared_data
648            .borrow_mut()
649            .insert(key1, account_data.clone());
650
651        let state = UpgradeableLoaderState::ProgramData {
652            slot: 0,
653            upgrade_authority_address: None,
654        };
655        let mut account_data2 = AccountSharedData::default();
656        account_data2.set_data(bincode::serialize(&state).unwrap());
657        mock_bank
658            .account_shared_data
659            .borrow_mut()
660            .insert(key2, account_data2.clone());
661
662        // This should return an error
663        let result = load_program_with_pubkey(
664            &mock_bank,
665            &batch_processor.get_environments_for_epoch(0).unwrap(),
666            &key1,
667            0,
668            &mut ExecuteTimings::default(),
669            false,
670        );
671        let loaded_program = ProgramCacheEntry::new_tombstone(
672            0,
673            ProgramCacheEntryOwner::LoaderV3,
674            ProgramCacheEntryType::FailedVerification(
675                batch_processor
676                    .get_environments_for_epoch(0)
677                    .unwrap()
678                    .program_runtime_v1,
679            ),
680        );
681        assert_eq!(result.unwrap(), Arc::new(loaded_program));
682
683        let mut buffer = load_test_program();
684        let mut header = bincode::serialize(&state).unwrap();
685        let mut complement = vec![
686            0;
687            std::cmp::max(
688                0,
689                UpgradeableLoaderState::size_of_programdata_metadata() - header.len()
690            )
691        ];
692        header.append(&mut complement);
693        header.append(&mut buffer);
694        account_data.set_data(header);
695
696        mock_bank
697            .account_shared_data
698            .borrow_mut()
699            .insert(key2, account_data.clone());
700
701        let result = load_program_with_pubkey(
702            &mock_bank,
703            &batch_processor.get_environments_for_epoch(20).unwrap(),
704            &key1,
705            200,
706            &mut ExecuteTimings::default(),
707            false,
708        );
709
710        let data = account_data.data();
711        account_data
712            .set_data(data[UpgradeableLoaderState::size_of_programdata_metadata()..].to_vec());
713
714        let environments = ProgramRuntimeEnvironments::default();
715        let expected = load_program_from_bytes(
716            &mut LoadProgramMetrics::default(),
717            account_data.data(),
718            account_data.owner(),
719            account_data.data().len(),
720            0,
721            environments.program_runtime_v1.clone(),
722            false,
723        );
724        assert_eq!(result.unwrap(), Arc::new(expected.unwrap()));
725    }
726
727    #[test]
728    fn test_load_program_of_loader_v4() {
729        let key = Pubkey::new_unique();
730        let mock_bank = MockBankCallback::default();
731        let mut account_data = AccountSharedData::default();
732        account_data.set_owner(loader_v4::id());
733        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
734
735        let loader_data = LoaderV4State {
736            slot: 0,
737            authority_address_or_next_version: Pubkey::new_unique(),
738            status: LoaderV4Status::Deployed,
739        };
740        let encoded = unsafe {
741            std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>(
742                &loader_data,
743            )
744        };
745        account_data.set_data(encoded.to_vec());
746        mock_bank
747            .account_shared_data
748            .borrow_mut()
749            .insert(key, account_data.clone());
750
751        let result = load_program_with_pubkey(
752            &mock_bank,
753            &batch_processor.get_environments_for_epoch(0).unwrap(),
754            &key,
755            0,
756            &mut ExecuteTimings::default(),
757            false,
758        );
759        let loaded_program = ProgramCacheEntry::new_tombstone(
760            0,
761            ProgramCacheEntryOwner::LoaderV4,
762            ProgramCacheEntryType::FailedVerification(
763                batch_processor
764                    .get_environments_for_epoch(0)
765                    .unwrap()
766                    .program_runtime_v1,
767            ),
768        );
769        assert_eq!(result.unwrap(), Arc::new(loaded_program));
770
771        let mut header = account_data.data().to_vec();
772        let mut complement =
773            vec![0; std::cmp::max(0, LoaderV4State::program_data_offset() - header.len())];
774        header.append(&mut complement);
775
776        let mut buffer = load_test_program();
777        header.append(&mut buffer);
778
779        account_data.set_data(header);
780        mock_bank
781            .account_shared_data
782            .borrow_mut()
783            .insert(key, account_data.clone());
784
785        let result = load_program_with_pubkey(
786            &mock_bank,
787            &batch_processor.get_environments_for_epoch(20).unwrap(),
788            &key,
789            200,
790            &mut ExecuteTimings::default(),
791            false,
792        );
793
794        let data = account_data.data()[LoaderV4State::program_data_offset()..].to_vec();
795        account_data.set_data(data);
796        mock_bank
797            .account_shared_data
798            .borrow_mut()
799            .insert(key, account_data.clone());
800
801        let environments = ProgramRuntimeEnvironments::default();
802        let expected = load_program_from_bytes(
803            &mut LoadProgramMetrics::default(),
804            account_data.data(),
805            account_data.owner(),
806            account_data.data().len(),
807            0,
808            environments.program_runtime_v1.clone(),
809            false,
810        );
811        assert_eq!(result.unwrap(), Arc::new(expected.unwrap()));
812    }
813
814    #[test]
815    fn test_load_program_environment() {
816        let key = Pubkey::new_unique();
817        let mock_bank = MockBankCallback::default();
818        let mut account_data = AccountSharedData::default();
819        account_data.set_owner(bpf_loader::id());
820        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
821
822        let upcoming_environments = ProgramRuntimeEnvironments::default();
823        let current_environments = {
824            let mut program_cache = batch_processor.program_cache.write().unwrap();
825            program_cache.upcoming_environments = Some(upcoming_environments.clone());
826            program_cache.environments.clone()
827        };
828        mock_bank
829            .account_shared_data
830            .borrow_mut()
831            .insert(key, account_data.clone());
832
833        for is_upcoming_env in [false, true] {
834            let result = load_program_with_pubkey(
835                &mock_bank,
836                &batch_processor
837                    .get_environments_for_epoch(is_upcoming_env as u64)
838                    .unwrap(),
839                &key,
840                200,
841                &mut ExecuteTimings::default(),
842                false,
843            )
844            .unwrap();
845            assert_ne!(
846                is_upcoming_env,
847                Arc::ptr_eq(
848                    result.program.get_environment().unwrap(),
849                    &current_environments.program_runtime_v1,
850                )
851            );
852            assert_eq!(
853                is_upcoming_env,
854                Arc::ptr_eq(
855                    result.program.get_environment().unwrap(),
856                    &upcoming_environments.program_runtime_v1,
857                )
858            );
859        }
860    }
861
862    #[test]
863    fn test_program_modification_slot_account_not_found() {
864        let mock_bank = MockBankCallback::default();
865
866        let key = Pubkey::new_unique();
867
868        let result = get_program_modification_slot(&mock_bank, &key);
869        assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound));
870
871        let mut account_data = AccountSharedData::new(100, 100, &bpf_loader_upgradeable::id());
872        mock_bank
873            .account_shared_data
874            .borrow_mut()
875            .insert(key, account_data.clone());
876
877        let result = get_program_modification_slot(&mock_bank, &key);
878        assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound));
879
880        let state = UpgradeableLoaderState::Program {
881            programdata_address: Pubkey::new_unique(),
882        };
883        account_data.set_data(bincode::serialize(&state).unwrap());
884        mock_bank
885            .account_shared_data
886            .borrow_mut()
887            .insert(key, account_data.clone());
888
889        let result = get_program_modification_slot(&mock_bank, &key);
890        assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound));
891
892        account_data.set_owner(loader_v4::id());
893        mock_bank
894            .account_shared_data
895            .borrow_mut()
896            .insert(key, account_data);
897
898        let result = get_program_modification_slot(&mock_bank, &key);
899        assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound));
900    }
901
902    #[test]
903    fn test_program_modification_slot_success() {
904        let mock_bank = MockBankCallback::default();
905
906        let key1 = Pubkey::new_unique();
907        let key2 = Pubkey::new_unique();
908
909        let account_data = AccountSharedData::new_data(
910            100,
911            &UpgradeableLoaderState::Program {
912                programdata_address: key2,
913            },
914            &bpf_loader_upgradeable::id(),
915        )
916        .unwrap();
917        mock_bank
918            .account_shared_data
919            .borrow_mut()
920            .insert(key1, account_data);
921
922        let account_data = AccountSharedData::new_data(
923            100,
924            &UpgradeableLoaderState::ProgramData {
925                slot: 77,
926                upgrade_authority_address: None,
927            },
928            &bpf_loader_upgradeable::id(),
929        )
930        .unwrap();
931        mock_bank
932            .account_shared_data
933            .borrow_mut()
934            .insert(key2, account_data);
935
936        let result = get_program_modification_slot(&mock_bank, &key1);
937        assert_eq!(result.unwrap(), 77);
938
939        let state = LoaderV4State {
940            slot: 58,
941            authority_address_or_next_version: Pubkey::new_unique(),
942            status: LoaderV4Status::Deployed,
943        };
944        let encoded = unsafe {
945            std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>(
946                &state,
947            )
948        };
949        let mut account_data = AccountSharedData::new(100, encoded.len(), &loader_v4::id());
950        account_data.set_data(encoded.to_vec());
951        mock_bank
952            .account_shared_data
953            .borrow_mut()
954            .insert(key1, account_data.clone());
955
956        let result = get_program_modification_slot(&mock_bank, &key1);
957        assert_eq!(result.unwrap(), 58);
958
959        account_data.set_owner(Pubkey::new_unique());
960        mock_bank
961            .account_shared_data
962            .borrow_mut()
963            .insert(key2, account_data);
964
965        let result = get_program_modification_slot(&mock_bank, &key2);
966        assert_eq!(result.unwrap(), 0);
967    }
968}