0xmiden/compiler/rust-sdk-testing-patterns

Claude Code and agent skill by 0xmiden/compiler

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Guide to testing Miden smart contracts with MockChain. Covers test setup, contract building, account/note creation, transaction execution, storage verification, faucet setup, output note verification, block numbering, multi-transaction tests, and asset-bearing notes. Use when writing, editing, or debugging Miden integration tests.

Install

npx vskill@latest install 0xmiden/compiler/rust-sdk-testing-patterns

SKILL.md

Miden Testing Patterns (MockChain)

These patterns target protocol/standards/testing 0.18.0 and VM/package crates in the 0.36 series. integration/Cargo.toml uses the released miden-client and miden-client-sqlite-store 0.18.0 crates from crates.io. Cargo.lock records the resolved crate versions.

MockChain and its builders live in miden-testing. This project depends on miden-testing directly; other repos may alternatively use the miden_client::testing re-export behind the client's optional testing feature.

The canonical worked examples are in the protocol repo itself: crates/miden-testing/src/kernel_tests/tx/test_note.rs for the end-to-end MockChain flow, and crates/miden-testing/src/mock_chain/{chain,chain_builder}.rs for the full builder surface.

Test File Setup

All tests are async and use MockChain for local execution without a network.

rust
use miden_client::{
account::{
component::{InitStorageData, StorageValueName},
AccountBuilder, AccountComponent, AccountType, StorageSlotName,
},
auth::AuthSchemeId,
note::NoteAssets,
transaction::RawOutputNote,
Felt, Word,
};
use miden_client::asset::{Asset, FungibleAsset};
use miden_testing::{AccountState, Auth, MockChain};

StorageValueName lives under account::component, never under account:: directly.

Step-by-Step Test Pattern

1. Initialize MockChain Builder

let mut builder = MockChain::builder();

2. Create Sender/Wallet Accounts

For a bare wallet, builder.add_existing_wallet(Auth::BasicAuth { auth_scheme: AuthSchemeId::Falcon512Poseidon2 }). For a pre-funded one, builder.add_existing_wallet_with_assets(auth, [FungibleAsset::new(faucet.id(), 100)?.into()]). Both return anyhow::Result<Account>.

Auth also offers Multisig, GuardedMultisig, MultisigSmart, IncrNonce, Noop, Conditional and NetworkAccount variants, plus the shorthands Auth::default(), Auth::basic_falcon() and Auth::basic_ecdsa().

Auth-scheme naming: miden_client::auth re-exports the same protocol enum under two names —
AuthScheme (the protocol name) and AuthSchemeId (an alias). Both compile; the field is
Auth::BasicAuth { auth_scheme }. The enum is #[non_exhaustive] with Falcon512Poseidon2 = 2
and EcdsaK256Keccak = 1, and the constants RPO_FALCON_SCHEME_ID /
ECDSA_K256_KECCAK_SCHEME_ID name them.
3. Set Up Faucets (for fungible assets)
rust
let faucet = builder.add_existing_basic_faucet(
Auth::BasicAuth {
auth_scheme: AuthSchemeId::Falcon512Poseidon2,
},
"TOKEN", // token symbol
1000, // max supply
Some(10), // token_supply (None defaults to 0)
)?;

The 4th argument is token_supply: Option<u64> (an explicit None is treated as 0).

4. Build Contracts

Build contracts out of process with the midenup / cargo-miden build command and load the resulting .masp package in the test. In this repository, build_project_in_dir(...) uses miden when MIDENUP_HOME is set, then CARGO_MIDEN, then cargo miden.

Do not add cargo-miden as a library dependency of a crate that also depends on miden-client. The build tool is intentionally outside the integration crate's Cargo graph; tests consume the compiled package through the client, standards, and miden-testing dependencies.

Package artefacts: the extension is .masp (Package::EXTENSION), magic MASP\0, package format version [6, 0, 0], MAST wire version [0, 0, 4]. There is no .masl.

5. Create Account with Storage

Storage slot naming convention (CRITICAL):

<[lib].namespace>::<field_name>

The slot name is part of the on-chain storage ABI and is derived by the compiler's #[component_storage] macro, not from the Rust struct name:

  • <[lib].namespace> is the three-segment Miden path declared in miden-project.toml, e.g.

miden::counter_contract::counter_contract. Because it comes from the declared namespace, renaming the Rust struct cannot change the deployed slot name.

  • <field_name> is the Rust storage field's identifier (not its description).

Live values from the compiler examples: miden::counter_contract::counter_contract::count_map, miden::auth_component_rpo_falcon512::auth_component::owner_public_key.

The component's storage is declared with the three-part component macro (#[component_storage] struct + #[component] trait + #[component] impl); the storage struct, not the trait, carries the #[storage] fields the slot names derive from. Externally-callable trait methods additionally need #[account_procedure]. See the rust-sdk-patterns skill for the contract side.

Seed any value slot that has no schema default, then register the account:

rust
// A `StorageValue<Word>` slot with no schema default must be seeded, or
// `AccountComponent::from_package` errors with `InitValueNotProvided`.
// A map slot defaults to empty and needs no entry.
let initialized_slot = StorageSlotName::new("bank_account::bank::initialized")?;
let mut init_storage_data = InitStorageData::default();
init_storage_data.insert_value(
StorageValueName::from_slot_name(&initialized_slot),
Word::default(),
)?;
let component = AccountComponent::from_package(&bank_package, &init_storage_data)?;
let account_builder = AccountBuilder::new([3u8; 32])
.account_type(AccountType::Public)
.with_component(component);
let bank_account = builder.add_account_from_builder(
Auth::BasicAuth { auth_scheme: AuthSchemeId::Falcon512Poseidon2 },
account_builder,
AccountState::Exists,
)?;

There is no `AccountBuilder::with_auth_component`. Auth components are installed like any other component, via .with_component(..) / .with_components(..). The builder's surface is new([u8; 32]), version, account_type, with_asset_callbacks, with_component(s), with_assets, nonce, storage_schemas, build, build_existing.

For map slots, seed entries with init_storage_data.insert_map_entry(slot_name, key, value)?.

Storage-seeding footgun: InitStorageData::insert_value(name, value) takes
value: impl Into<WordValue>. The numeric From impls (u8/u16/u32/u64) produce a
WordValue::Atomic(string) that the slot's schema parses — not a felt-positioned Word. Only
From<Felt> yields [felt, 0, 0, 0], and From<Word> / From<[Felt; 4]> / From<[u32; 4]> are
fully-typed words. For a StorageValue<Word> slot whose contract reads index [0], seed a Word
(Word::default() for zero). The insert_value doc comment claiming u64 becomes [0,0,0,felt]
is inaccurate; the code produces an atomic string.
Account model:
- AccountType is the visibility enum { Private (default), Public }, with as_u8(),
is_public(), is_private().
- Set visibility via .account_type(..). There is no .storage_mode(..) and no
AccountStorageMode on this builder.
- Faucet-ness is determined by the installed components.

MockChainBuilder also ships ready-made account helpers that remove most of the above: create_new_wallet, add_existing_note_creator, add_existing_non_fungible_faucet, add_existing_network_faucet, create_new_faucet, add_existing_mock_account (and its _with_storage / _with_assets / _with_storage_and_assets variants), add_existing_account_from_components.

6. Create Notes

Build a note with NoteBuilder, seeding a rand generator from the note-script root:

rust
use miden_client::{asset::FungibleAsset, note::NoteScript, Felt, Word};
use miden_standards::testing::note::NoteBuilder;
use rand::{rngs::StdRng, SeedableRng};
let note_script = NoteScript::from_package(note_package.as_ref())?;
let mut note_rng = StdRng::from_seed(Word::from(note_script.root()).into());
let note = NoteBuilder::new(sender.id(), &mut note_rng)
.package((*note_package).clone())
.add_assets([FungibleAsset::new(faucet.id(), 50)?.into()])
.note_storage([Felt::from(42_u32), Felt::from(0_u32)])?
.build()?;

NoteBuilder::new(sender: AccountId, rng: T) takes the RNG by value; &mut StdRng works because &mut T: Rng. miden_client::crypto::RandomCoin is no longer re-exported by the client. Other builder methods: package, script, code, note_type, tag, add_assets, note_storage, serial_number, attachment, advice_map, dynamically_linked_packages, source_manager, build.

.tag(..) takes a `u32`, not a NoteTag. The idiom is
.tag(NoteTag::with_account_target(account.id()).into()).
NoteScript::from_package(&Package) requires the package to have exactly one @note_script
export. NoteScript::root() returns a NoteScriptRoot newtype, and the 32-byte seed comes from a
Word, so convert explicitly with Word::from(...root()).into().
Felt::new(u64) is fallible — it returns Result<Felt, FeltFromIntError>. note_storage
takes impl IntoIterator<Item = Felt>, so build each felt with the infallible
Felt::from(42_u32) for in-range literals (From<u8>/<u16>/<u32> are infallible); for an
arbitrary u64, use Felt::new(n)?. Use Felt::new_unchecked(n) only after proving
n < Felt::ORDER.
A note carries at most MAX_ASSETS_PER_NOTE = 16 assets.

MockChainBuilder also has ready-made note constructors that skip NoteBuilder entirely: add_p2any_note, add_p2id_note, add_p2ide_note, add_swap_note, add_spawn_note, add_tx_fee_note, add_p2id_note_with_fee.

Standard notes built outside the chain builder use typed bon builders — P2idNote::builder().sender(..).target(..).serial_number(..).asset(..).build()?, then Note::from(p2id). There is no XNote::create(..).

7. Add to MockChain and Build

Register accounts (builder.add_account(account)?) and seed notes (builder.add_output_note(RawOutputNote::Full(note.clone()))), then let mut mock_chain = builder.build()?;.

add_output_note returns (), not a Result — no ?.
8. Execute Transaction
rust
let executed = mock_chain
.build_transaction(account.id())
.authenticated_input_note(note.id())
.build()?
.execute()
.await?;
mock_chain.add_pending_executed_transaction(&executed)?;
mock_chain.prove_next_block()?;

build_transaction(input) takes impl Into<MockTransactionInput>, which has From<AccountId> and From<Account>. Pass an Account (rather than an id) when chaining transactions against evolving in-memory state, and for private accounts.

Input notes are not positional arguments. Attach them with .authenticated_input_note(NoteId), .authenticated_input_notes(..), .unauthenticated_input_note(Note) or .unauthenticated_input_notes(..).

build() is sync and returns anyhow::Result<MockTransaction>; execute() is on MockTransaction, is async, and returns Result<ExecutedTransaction, TransactionExecutorError>.

Other MockTransactionBuilder methods: tx_script, tx_script_args, auth_args, extend_note_args, reference_block, foreign_accounts, extend_advice_inputs, add_advice_map_entry, authenticator, add_signature, add_note_script, send_notes_script, expected_output_note(s), with_source_manager.

9. Execute with Transaction Script

TransactionScript::from_package(&package)? handles a kind = "tx-script" package directly: if the package is a program it uses the entrypoint, otherwise it looks for the single procedure carrying the transaction_script attribute, which the compiler emits on tx-script exports.

rust
use miden_client::transaction::TransactionScript;
let tx_script = TransactionScript::from_package(&tx_script_package)?;
let executed = mock_chain
.build_transaction(account.id())
.tx_script(tx_script)
.build()?
.execute()
.await?;
mock_chain.add_pending_executed_transaction(&executed)?;
mock_chain.prove_next_block()?;
let updated_account = mock_chain.committed_account(account.id())?;

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
116
Repository forks
86
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/0xmiden/compiler/rust-sdk-testing-patterns/badge)](https://verified-skill.com/skills/0xmiden/compiler/rust-sdk-testing-patterns)

Something wrong with this skill or its page? Tell us.