Skip to main content

mollusk_svm/
program.rs

1//! Module for working with Solana programs.
2
3use {
4    agave_feature_set::FeatureSet,
5    agave_syscalls::create_program_runtime_environment_v1,
6    solana_account::Account,
7    solana_compute_budget::compute_budget::ComputeBudget,
8    solana_loader_v3_interface::state::UpgradeableLoaderState,
9    solana_loader_v4_interface::state::{LoaderV4State, LoaderV4Status},
10    solana_program_runtime::{
11        invoke_context::{BuiltinFunctionWithContext, InvokeContext},
12        loaded_programs::{LoadProgramMetrics, ProgramCacheEntry, ProgramCacheForTxBatch},
13        solana_sbpf::program::BuiltinProgram,
14    },
15    solana_pubkey::Pubkey,
16    solana_rent::Rent,
17    std::{
18        cell::{RefCell, RefMut},
19        collections::HashMap,
20        rc::Rc,
21        sync::Arc,
22    },
23};
24
25/// Loader keys, re-exported from `solana_sdk` for convenience.
26pub mod loader_keys {
27    pub use solana_sdk_ids::{
28        bpf_loader::ID as LOADER_V2, bpf_loader_deprecated::ID as LOADER_V1,
29        bpf_loader_upgradeable::ID as LOADER_V3, loader_v4::ID as LOADER_V4,
30        native_loader::ID as NATIVE_LOADER,
31    };
32}
33
34#[cfg(feature = "precompiles")]
35pub mod precompile_keys {
36    use solana_pubkey::Pubkey;
37    pub use solana_sdk_ids::{
38        ed25519_program::ID as ED25519_PROGRAM, secp256k1_program::ID as SECP256K1_PROGRAM,
39        secp256r1_program::ID as SECP256R1_PROGRAM,
40    };
41
42    pub(crate) fn is_precompile(program_id: &Pubkey) -> bool {
43        matches!(
44            *program_id,
45            ED25519_PROGRAM | SECP256K1_PROGRAM | SECP256R1_PROGRAM
46        )
47    }
48}
49
50#[cfg(not(feature = "precompiles"))]
51pub mod precompile_keys {
52    use solana_pubkey::Pubkey;
53
54    pub(crate) const fn is_precompile(_program_id: &Pubkey) -> bool {
55        false
56    }
57}
58
59pub struct CacheEntry {
60    pub loader_key: Pubkey,
61    pub elf_bytes: Option<Vec<u8>>,
62}
63
64pub struct ProgramCache {
65    cache: Rc<RefCell<ProgramCacheForTxBatch>>,
66    // This stinks, but the `ProgramCacheForTxBatch` doesn't offer a way to
67    // access its entries directly. In order to make DX easier for those using
68    // `MolluskContext`, we need to track entries added to the cache,
69    // so we can populate the account store with program accounts.
70    // This saves the developer from having to pre-load the account store with
71    // all program accounts they may use, when `Mollusk` has that information
72    // already.
73    //
74    // K: program ID, V: cache entry
75    entries_cache: Rc<RefCell<HashMap<Pubkey, CacheEntry>>>,
76    // The function registry (syscalls) to use for verifying and loading
77    // program ELFs.
78    pub program_runtime_environment: BuiltinProgram<InvokeContext<'static, 'static>>,
79}
80
81impl ProgramCache {
82    pub fn new(
83        feature_set: &FeatureSet,
84        compute_budget: &ComputeBudget,
85        enable_register_tracing: bool,
86    ) -> Self {
87        let me = Self {
88            cache: Rc::new(RefCell::new(ProgramCacheForTxBatch::default())),
89            entries_cache: Rc::new(RefCell::new(HashMap::new())),
90            program_runtime_environment: create_program_runtime_environment_v1(
91                &feature_set.runtime_features(),
92                &compute_budget.to_budget(),
93                /* reject_deployment_of_broken_elfs */ false,
94                /* debugging_features */ enable_register_tracing,
95            )
96            .unwrap(),
97        };
98        BUILTINS.iter().for_each(|builtin| {
99            let program_id = builtin.program_id;
100            let entry = builtin.program_cache_entry();
101            me.replenish(program_id, entry, None);
102        });
103        me
104    }
105
106    pub(crate) fn cache(&self) -> RefMut<'_, ProgramCacheForTxBatch> {
107        self.cache.borrow_mut()
108    }
109
110    fn replenish(
111        &self,
112        program_id: Pubkey,
113        entry: Arc<ProgramCacheEntry>,
114        elf_bytes: Option<&[u8]>,
115    ) {
116        self.entries_cache.borrow_mut().insert(
117            program_id,
118            CacheEntry {
119                loader_key: entry.account_owner(),
120                elf_bytes: elf_bytes.map(|s| s.to_vec()),
121            },
122        );
123        self.cache.borrow_mut().replenish(program_id, entry);
124    }
125
126    /// Add a builtin program to the cache.
127    pub fn add_builtin(&mut self, builtin: Builtin) {
128        let program_id = builtin.program_id;
129        let entry = builtin.program_cache_entry();
130        self.replenish(program_id, entry, None);
131    }
132
133    /// Add a program to the cache.
134    pub fn add_program(&mut self, program_id: &Pubkey, loader_key: &Pubkey, elf: &[u8]) {
135        // This might look rough, but it's actually functionally the same as
136        // calling `create_program_runtime_environment_v1` on every addition.
137        let environment = {
138            let config = self.program_runtime_environment.get_config().clone();
139            let mut loader = BuiltinProgram::new_loader(config);
140
141            for (_key, (name, value)) in self
142                .program_runtime_environment
143                .get_function_registry()
144                .iter()
145            {
146                let name = std::str::from_utf8(name).unwrap();
147                loader.register_function(name, value).unwrap();
148            }
149
150            Arc::new(loader)
151        };
152        self.replenish(
153            *program_id,
154            Arc::new(
155                ProgramCacheEntry::new(
156                    loader_key,
157                    environment,
158                    0,
159                    0,
160                    elf,
161                    elf.len(),
162                    &mut LoadProgramMetrics::default(),
163                )
164                .unwrap(),
165            ),
166            Some(elf),
167        );
168    }
169
170    /// Load a program from the cache.
171    pub fn load_program(&self, program_id: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
172        self.cache.borrow().find(program_id)
173    }
174
175    // NOTE: These are only stubs. This will "just work", since Agave's SVM
176    // stubs out program accounts in transaction execution already, noting that
177    // the ELFs are already where they need to be: in the cache.
178    pub(crate) fn get_all_keyed_program_accounts(&self) -> Vec<(Pubkey, Account)> {
179        self.entries_cache
180            .borrow()
181            .iter()
182            .map(|(program_id, cache_entry)| match cache_entry.loader_key {
183                loader_keys::NATIVE_LOADER => {
184                    create_keyed_account_for_builtin_program(program_id, "I'm a stub!")
185                }
186                loader_keys::LOADER_V1 => (*program_id, create_program_account_loader_v1(&[])),
187                loader_keys::LOADER_V2 => (*program_id, create_program_account_loader_v2(&[])),
188                loader_keys::LOADER_V3 => {
189                    (*program_id, create_program_account_loader_v3(program_id))
190                }
191                loader_keys::LOADER_V4 => (*program_id, create_program_account_loader_v4(&[])),
192                _ => panic!("Invalid loader key: {}", cache_entry.loader_key),
193            })
194            .collect()
195    }
196
197    pub(crate) fn maybe_create_program_account(&self, pubkey: &Pubkey) -> Option<Account> {
198        // If it's found in the entries cache, create the proper program account based
199        // on the loader key.
200        self.entries_cache
201            .borrow()
202            .get(pubkey)
203            .map(|cache_entry| match cache_entry.loader_key {
204                loader_keys::NATIVE_LOADER => {
205                    create_keyed_account_for_builtin_program(pubkey, "I'm a stub!").1
206                }
207                loader_keys::LOADER_V1 => create_program_account_loader_v1(&[]),
208                loader_keys::LOADER_V2 => create_program_account_loader_v2(&[]),
209                loader_keys::LOADER_V3 => create_program_account_loader_v3(pubkey),
210                loader_keys::LOADER_V4 => create_program_account_loader_v4(&[]),
211                _ => panic!("Invalid loader key: {}", cache_entry.loader_key),
212            })
213    }
214
215    pub fn get_program_elf_bytes(&self, program_id: &Pubkey) -> Option<Vec<u8>> {
216        match self.entries_cache.borrow().get(program_id) {
217            None => None,
218            Some(cache_entry) => cache_entry.elf_bytes.to_owned(),
219        }
220    }
221}
222
223pub struct Builtin {
224    pub program_id: Pubkey,
225    pub name: &'static str,
226    pub entrypoint: BuiltinFunctionWithContext,
227}
228
229impl Builtin {
230    fn program_cache_entry(&self) -> Arc<ProgramCacheEntry> {
231        Arc::new(ProgramCacheEntry::new_builtin(
232            0,
233            self.name.len(),
234            self.entrypoint,
235        ))
236    }
237}
238
239static BUILTINS: &[Builtin] = &[
240    Builtin {
241        program_id: solana_system_program::id(),
242        name: "system_program",
243        entrypoint: solana_system_program::system_processor::Entrypoint::vm,
244    },
245    Builtin {
246        program_id: loader_keys::LOADER_V2,
247        name: "solana_bpf_loader_program",
248        entrypoint: solana_bpf_loader_program::Entrypoint::vm,
249    },
250    Builtin {
251        program_id: loader_keys::LOADER_V3,
252        name: "solana_bpf_loader_upgradeable_program",
253        entrypoint: solana_bpf_loader_program::Entrypoint::vm,
254    },
255    #[cfg(feature = "all-builtins")]
256    Builtin {
257        program_id: loader_keys::LOADER_V1,
258        name: "solana_bpf_loader_deprecated_program",
259        entrypoint: solana_bpf_loader_program::Entrypoint::vm,
260    },
261    #[cfg(feature = "all-builtins")]
262    Builtin {
263        program_id: loader_keys::LOADER_V4,
264        name: "solana_loader_v4_program",
265        entrypoint: solana_loader_v4_program::Entrypoint::vm,
266    },
267    #[cfg(feature = "all-builtins")]
268    Builtin {
269        program_id: solana_sdk_ids::zk_elgamal_proof_program::id(),
270        name: "zk_elgamal_proof_program",
271        entrypoint: solana_zk_elgamal_proof_program::Entrypoint::vm,
272    },
273    #[cfg(feature = "all-builtins")]
274    Builtin {
275        program_id: solana_sdk_ids::compute_budget::id(),
276        name: "compute_budget_program",
277        entrypoint: solana_compute_budget_program::Entrypoint::vm,
278    },
279    #[cfg(feature = "all-builtins")]
280    Builtin {
281        program_id: solana_sdk_ids::vote::id(),
282        name: "vote_program",
283        entrypoint: solana_vote_program::vote_processor::Entrypoint::vm,
284    },
285];
286
287/// Create a key and account for a builtin program.
288pub fn create_keyed_account_for_builtin_program(
289    program_id: &Pubkey,
290    name: &str,
291) -> (Pubkey, Account) {
292    let data = name.as_bytes().to_vec();
293    let lamports = Rent::default().minimum_balance(data.len());
294    let account = Account {
295        lamports,
296        data,
297        owner: loader_keys::NATIVE_LOADER,
298        executable: true,
299        ..Default::default()
300    };
301    (*program_id, account)
302}
303
304/// Get the key and account for the system program.
305pub fn keyed_account_for_system_program() -> (Pubkey, Account) {
306    create_keyed_account_for_builtin_program(&BUILTINS[0].program_id, BUILTINS[0].name)
307}
308
309/// Get the key and account for the BPF Loader v2 program.
310pub fn keyed_account_for_bpf_loader_v2_program() -> (Pubkey, Account) {
311    create_keyed_account_for_builtin_program(&BUILTINS[1].program_id, BUILTINS[1].name)
312}
313
314/// Get the key and account for the BPF Loader v3 (Upgradeable) program.
315pub fn keyed_account_for_bpf_loader_v3_program() -> (Pubkey, Account) {
316    create_keyed_account_for_builtin_program(&BUILTINS[2].program_id, BUILTINS[2].name)
317}
318
319/* ... */
320
321/// Create a BPF Loader 1 (deprecated) program account.
322pub fn create_program_account_loader_v1(elf: &[u8]) -> Account {
323    let lamports = Rent::default().minimum_balance(elf.len());
324    Account {
325        lamports,
326        data: elf.to_vec(),
327        owner: loader_keys::LOADER_V1,
328        executable: true,
329        ..Default::default()
330    }
331}
332
333/// Create a BPF Loader 2 program account.
334pub fn create_program_account_loader_v2(elf: &[u8]) -> Account {
335    let lamports = Rent::default().minimum_balance(elf.len());
336    Account {
337        lamports,
338        data: elf.to_vec(),
339        owner: loader_keys::LOADER_V2,
340        executable: true,
341        ..Default::default()
342    }
343}
344
345/// Create a BPF Loader v3 (Upgradeable) program account.
346pub fn create_program_account_loader_v3(program_id: &Pubkey) -> Account {
347    let programdata_address =
348        Pubkey::find_program_address(&[program_id.as_ref()], &loader_keys::LOADER_V3).0;
349    let data = bincode::serialize(&UpgradeableLoaderState::Program {
350        programdata_address,
351    })
352    .unwrap();
353    let lamports = Rent::default().minimum_balance(data.len());
354    Account {
355        lamports,
356        data,
357        owner: loader_keys::LOADER_V3,
358        executable: true,
359        ..Default::default()
360    }
361}
362
363/// Create a BPF Loader v3 (Upgradeable) program data account.
364pub fn create_program_data_account_loader_v3(elf: &[u8]) -> Account {
365    let data = {
366        let elf_offset = UpgradeableLoaderState::size_of_programdata_metadata();
367        let data_len = elf_offset + elf.len();
368        let mut data = vec![0; data_len];
369        bincode::serialize_into(
370            &mut data[0..elf_offset],
371            &UpgradeableLoaderState::ProgramData {
372                slot: 0,
373                upgrade_authority_address: None,
374            },
375        )
376        .unwrap();
377        data[elf_offset..].copy_from_slice(elf);
378        data
379    };
380    let lamports = Rent::default().minimum_balance(data.len());
381    Account {
382        lamports,
383        data,
384        owner: loader_keys::LOADER_V3,
385        executable: false,
386        ..Default::default()
387    }
388}
389
390/// Create a BPF Loader v3 (Upgradeable) program and program data account.
391///
392/// Returns a tuple, where the first element is the program account and the
393/// second element is the program data account.
394pub fn create_program_account_pair_loader_v3(
395    program_id: &Pubkey,
396    elf: &[u8],
397) -> (Account, Account) {
398    (
399        create_program_account_loader_v3(program_id),
400        create_program_data_account_loader_v3(elf),
401    )
402}
403
404/// Create a BPF Loader 4 program account.
405pub fn create_program_account_loader_v4(elf: &[u8]) -> Account {
406    let data = unsafe {
407        let elf_offset = LoaderV4State::program_data_offset();
408        let data_len = elf_offset + elf.len();
409        let mut data = vec![0u8; data_len];
410        *std::mem::transmute::<&mut [u8; LoaderV4State::program_data_offset()], &mut LoaderV4State>(
411            (&mut data[0..elf_offset]).try_into().unwrap(),
412        ) = LoaderV4State {
413            slot: 0,
414            authority_address_or_next_version: Pubkey::new_from_array([2; 32]),
415            status: LoaderV4Status::Deployed,
416        };
417        data[elf_offset..].copy_from_slice(elf);
418        data
419    };
420    let lamports = Rent::default().minimum_balance(data.len());
421    Account {
422        lamports,
423        data,
424        owner: loader_keys::LOADER_V4,
425        executable: true,
426        ..Default::default()
427    }
428}