Skip to content

Conversation

@L0STE
Copy link
Contributor

@L0STE L0STE commented Nov 3, 2025

This crate simplifies testing by fetching and storing mainnet accounts directly in the AccountStore.

Key Features

The new RpcAccountStore allows you to:

  • Pass mock accounts using the with_accounts method
  • Fetch mainnet accounts from instructions using the from_instruction method to automatically dump all accounts referenced in an instruction
  • Handle missing accounts gracefully with the allow_missing_accounts method, which stores them with default values
  • Add CPI programs using the add_programs method, which fetches program data from the account store and loads it via mollusk.add_program_with_elf_and_loader (supports both loader V2 and V3)
  • Sync slots with mainnet using with_synced_slot for oracles that require the current mainnet slot to function correctly

New Harness

Added a new test harness variant with *_with_context methods that implement all the above helpers. These methods support both strict and non-strict modes depending on how they're called, while maintaining the same interface as the original harness.

Copy link
Collaborator

@buffalojoec buffalojoec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty nifty!

My biggest concern is that it reimplements a lot of the harness's API. I would rather figure out how we can get this to play nice with the AccountStore trait and then it can be supported by the generic framework. I left a comment about it.

Thanks for putting this up, though! Seems very useful.

@@ -0,0 +1,20 @@
[package]
name = "mollusk-svm-on-demand"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to bikeshed the name. 😅

This library tends to use more descriptive (sometimes verbose) naming conventions, so it's clear what a given crate does. "On demand" is pretty vague in my opinion.

What about something like one of these?

  • mollusk-account-store-on-demand
  • mollusk-account-store-rpc-on-demand
  • mollusk-account-store-rpc

@@ -0,0 +1,20 @@
[package]
name = "mollusk-svm-on-demand"
description = "Automatically fetch and use mainnet accounts with the Mollusk SVM harness."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it only mainnet-beta?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, it's not. Fixing

//! RPC account store for fetching accounts from Solana RPC endpoints.
//!
//! This module provides the `RpcAccountStore` type for automatically fetching
//! accounts from mainnet and managing them for use with Mollusk testing.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't appear the tool is limited to mainnet. Why should it be, anyway?

Comment on lines +170 to +171
self.fetch_accounts(&pubkeys.into_iter().collect::<Vec<_>>())
.await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since fetch_accounts is internal, you could just make it accept a pubkey iterator (impl Iterator<Item = Pubkey>). You're also deduping in the fetch_accounts as well, so no need for the hash map above this. Just pipe the mapped iterator over the instruction accounts directly into fetch_accounts!

return Ok(());
}

let accounts = self.client.get_multiple_accounts(&missing_pubkeys).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, I suppose you maybe do have to do one allocation, but perhaps you can filter by cache.contains_key and collect that into a vec, then pass to the RPC call?

Comment on lines +65 to +83
pub async fn process_instruction_with_context(
rpc_url: &str,
mut mollusk: Mollusk,
instruction: &Instruction,
accounts: &[(Pubkey, Account)],
) -> Result<InstructionResult> {
let cache = RpcAccountStore::new(rpc_url)
.allow_missing_accounts()
.skip_program_validation()
.with_accounts(accounts)
.from_instruction(instruction)
.await?
.add_programs(&mut mollusk)
.await?
.cache;

let context = mollusk.with_context(cache);
Ok(context.process_instruction(instruction))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just let developers create an RpcAccountStore and then do this?

let store = RpcAccountStore::new(rpc_url)
        .allow_missing_accounts()
        .skip_program_validation();

let mollusk = Mollusk::default().with_context(&store);

mollusk.process_instruction(
    &instruction,
    &accounts,
);

You just need to implement AccountStore for RpcAccountStore.

pub trait AccountStore {

Is it because your fetcher is async? Can you use the blocking RPC client instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I maybe should have made it more clear but this is currently possible.

You just need to add an .await() at the end of the store implementation and you can then blast it in the normal harness

/// This function fetches the program data accounts for all programs that are
/// stored in the cache and adds them to the Mollusk environment.
///
/// Note: This is needed because mollusk-svm doesn't load the programs for CPIs directly from the accounts.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, maybe it should (?)

@L0STE
Copy link
Contributor Author

L0STE commented Nov 21, 2025

My biggest concern is that it reimplements a lot of the harness's API.

Agree, I don't love that as well.

Tbh all current methods works without having to reimplement the harness, I added that after just to make it easier to use for people that don't really know how to do so. But probably it's just more confusing and just midcurved the entire implementation.

I'm down to nuke the reimplementation of the harness

@buffalojoec
Copy link
Collaborator

@L0STE what do you think about something like this?
https://github.com/anza-xyz/mollusk/tree/poc-simplify-on-demand

I just went with the blocking RPC client and implemented AccountStore, so the native support for account stores will just work with this thing.

What you could do it configure it to either
a) Only support pre-loading of accounts from RPC, then it's just an account store that serves Account::default() for any not found.
b) Make the implementation of default_account go out to the RPC for any accounts not found in the store, defaulting to Account::default() if not found on-chain (my last commit to the PoC branch).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants