Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 19 additions & 35 deletions .claude/skills/rust-sdk-patterns/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rust-sdk-patterns
description: Complete guide to writing Miden smart contracts with the Rust SDK. Covers #[component], #[note], #[tx_script] macros, storage patterns, native functions, asset handling, cross-component calls, and P2ID note creation. Use when writing, editing, or reviewing Miden Rust contract code.
description: Complete guide to writing Miden smart contracts with the Rust SDK. Covers #[component], #[note], #[tx_script] macros, storage patterns, native functions, asset handling, cross-component calls, P2ID note creation, and asset receiving via component methods. Use when writing, editing, or reviewing Miden Rust contract code.
---

# Miden Rust SDK Patterns
Expand Down Expand Up @@ -53,7 +53,7 @@ fn run(_arg: Word, account: &mut Account) {
|--------|--------------|---------|
| `native_account::` | `add_asset(Asset)`, `remove_asset(Asset)`, `incr_nonce()` | Modify account vault/nonce |
| `active_account::` | `get_id() -> AccountId`, `get_balance(AccountId) -> Felt` | Query current account |
| `active_note::` | `get_inputs() -> Vec<Felt>`, `get_assets() -> Vec<Asset>`, `get_sender() -> AccountId` | Query note being consumed |
| `active_note::` | `get_storage() -> Vec<Felt>`, `get_assets() -> Vec<Asset>`, `get_sender() -> AccountId` | Query note being consumed |
| `output_note::` | `create(Tag, NoteType, Recipient) -> NoteIdx`, `add_asset(Asset, NoteIdx)` | Create output notes |
| `faucet::` | `create_fungible_asset(Felt) -> Asset`, `mint(Asset)`, `burn(Asset)` | Asset minting |
| `tx::` | `get_block_number() -> Felt`, `get_block_timestamp() -> Felt` | Transaction context |
Expand All @@ -63,11 +63,15 @@ fn run(_arg: Word, account: &mut Account) {

Fungible asset Word layout: `[amount, 0, faucet_suffix, faucet_prefix]`

**Constructor**: `Asset::new(word)` creates an Asset from a Word.

See [miden-bank bank-account](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for complete asset handling patterns including deposit, withdrawal, and balance tracking.

```rust
// Access asset amount
let amount = asset.inner[0];

// Add asset to account vault
// Add asset to account vault (only from component methods, not note scripts — see pitfall P9)
native_account::add_asset(asset);

// Remove asset from account vault
Expand All @@ -76,35 +80,7 @@ native_account::remove_asset(asset.clone());

## P2ID Output Note Creation

To send assets to another account, create a P2ID (Pay-to-ID) output note:

```rust
fn create_p2id_note(&mut self, serial_num: Word, asset: &Asset,
recipient_id: AccountId, tag: Felt, note_type: Felt) {
let tag = Tag::from(tag);
let note_type = NoteType::from(note_type);
let script_root = Self::p2id_note_root(); // Hardcoded P2ID script digest

// P2ID inputs: [suffix, prefix] of recipient
let recipient = Recipient::compute(serial_num, script_root,
vec![recipient_id.suffix, recipient_id.prefix]);

let note_idx = output_note::create(tag, note_type, recipient);
native_account::remove_asset(asset.clone());
output_note::add_asset(asset.clone(), note_idx);
}
```

## Note Inputs

Notes receive data via inputs (Vec<Felt>), accessed with `active_note::get_inputs()`:

```rust
let inputs = active_note::get_inputs();
// Parse: Asset = inputs[0..4], serial_num = inputs[4..8], tag = inputs[8], type = inputs[9]
let asset = Asset::new(Word::from([inputs[0], inputs[1], inputs[2], inputs[3]]));
let serial_num = Word::from([inputs[4], inputs[5], inputs[6], inputs[7]]);
```
To send assets to another account, create a P2ID (Pay-to-ID) output note. See [miden-bank bank-account](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) `create_p2id_note()` for a complete working implementation.

## Cross-Component Dependencies

Expand All @@ -116,10 +92,10 @@ Then import the bindings in your Rust code. See [increment-note/src/lib.rs](../.

```rust
// Felt from integer
let f = felt!(42);
let f = Felt::new(42);
let f = felt!(42); // preferred for literals in contract code
let f = Felt::new(42); // construct a Felt from a u64
let f = Felt::from_u32(42);
let f = Felt::from_u64_unchecked(42);
let f = Felt::from_u64_unchecked(42); // when value is known < field modulus

// Word from Felts
let w = Word::from([f0, f1, f2, f3]);
Expand All @@ -140,6 +116,14 @@ extern crate alloc;
use alloc::vec::Vec;
```

## Asset Receiving via Component Methods

Note scripts cannot call `native_account::add_asset()` directly (see pitfall P9). The canonical pattern is for an account component to expose a public method that wraps `native_account::add_asset()`, and note scripts call that method via cross-component bindings.

See [miden-bank bank-account deposit()](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for the component side: the `deposit()` method validates the deposit, updates storage, and calls `native_account::add_asset()`.

See [miden-bank deposit-note](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/deposit-note/src/lib.rs) for the note side: the note script calls `bank_account::deposit()` via generated bindings.

## Validation Checklist

- [ ] `#![no_std]` and `#![feature(alloc_error_handler)]` at top of every contract
Expand Down
39 changes: 37 additions & 2 deletions .claude/skills/rust-sdk-pitfalls/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rust-sdk-pitfalls
description: Critical pitfalls and safety rules for Miden Rust SDK development. Covers felt arithmetic security, comparison operators, argument limits, storage naming, no-std setup, asset layout, and P2ID roots. Use when reviewing, debugging, or writing Miden contract code.
description: Critical pitfalls and safety rules for Miden Rust SDK development. Covers felt arithmetic security, comparison operators, argument limits, storage naming, no-std setup, asset layout, P2ID roots, NoteType construction, note-to-component call boundaries, and note input immutability. Use when reviewing, debugging, or writing Miden contract code.
---

# Miden SDK Pitfalls
Expand All @@ -24,6 +24,8 @@ let new_balance = current_balance - withdraw_amount;

**Rule**: ALWAYS check `.as_u64()` values before any Felt subtraction.

**Max Felt value**: The maximum valid Felt is `p - 1 = 18446744069414584320`, not `u64::MAX` (`18446744073709551615`). Using `u64::MAX` as a sentinel or boundary value causes silent wraparound.

## P2: Felt Comparison Operators Are Misleading for Quantity Logic

**Severity**: High — silently produces incorrect results
Expand Down Expand Up @@ -131,6 +133,36 @@ fn p2id_note_root() -> Digest {

**Mitigation**: Use `P2idNote::script_root()` from miden-standards if available, or verify the hardcoded root matches the current version after dependency updates.

**NoteType for P2ID**: P2ID output notes created in contract code should use the private note type value via `NoteType::from(felt!(2))` (see P8). Using the public note type triggers an opaque "missing details in advice provider" error at execution time. See [miden-bank withdraw](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for the working pattern.

## P8: NoteType Variants Unavailable in Compiler SDK

**Severity**: Medium -- causes compilation errors

Named enum variants (`NoteType::Private`, `NoteType::Public`, `NoteType::Encrypted`) don't exist in contract code. Construct via `NoteType::from()`:

| NoteType | Value |
|----------|-------|
| Public | `NoteType::from(felt!(1))` |
| Private | `NoteType::from(felt!(2))` |
| Encrypted | `NoteType::from(felt!(3))` |

See [miden-bank bank-account](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for `NoteType::from(note_type)` usage.

## P9: Note Scripts Cannot Call Native Account Functions

**Severity**: High -- causes runtime failures

Note scripts cannot call `native_account::add_asset()` or other `native_account::` functions directly. The kernel's `authenticate_account_origin` check rejects these calls from a note context. Instead, note scripts must call an account component method, which then calls `native_account::add_asset()` internally.

See [miden-bank deposit-note](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/deposit-note/src/lib.rs) for the correct pattern: the note script calls `bank_account::deposit()`, which internally calls `native_account::add_asset()`.

## P10: Note Inputs Are Immutable After Creation

**Severity**: Low -- causes incorrect architecture

Note inputs (`active_note::get_storage()`) are baked at note creation time and cannot be modified after creation. Design note input layouts carefully before deployment.

## Quick Reference

| Pitfall | One-Line Rule |
Expand All @@ -141,4 +173,7 @@ fn p2id_note_root() -> Digest {
| P4 Storage names | `miden::component::pkg_name::field` (underscores) |
| P5 No-std | `#![no_std]` + `#![feature(alloc_error_handler)]` |
| P6 Asset layout | `[amount, 0, suffix, prefix]` |
| P7 P2ID root | Verify digest after dependency updates |
| P7 P2ID root | Verify digest after dependency updates; use `NoteType::from(felt!(2))` for private |
| P8 NoteType | No named variants in contracts — use `NoteType::from(felt!(n))` |
| P9 Note ↛ native_account | Note scripts must call component methods, not `native_account::` |
| P10 Note inputs | Immutable after creation — design layouts upfront |
71 changes: 33 additions & 38 deletions .claude/skills/rust-sdk-testing-patterns/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rust-sdk-testing-patterns
description: Guide to testing Miden smart contracts with MockChain. Covers test setup, contract building, account/note creation, transaction execution, storage verification, faucet setup, and output note verification. Use when writing, editing, or debugging Miden integration tests.
description: 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.
---

# Miden Testing Patterns (MockChain)
Expand All @@ -27,7 +27,7 @@ let faucet = builder.add_existing_basic_faucet(
Auth::BasicAuth,
"TOKEN", // token symbol
1000, // max supply
Some(10), // decimals (None for 0)
Some(10), // total_issuance (None for 0)
)?;
```

Expand Down Expand Up @@ -66,7 +66,7 @@ See [counter_test.rs](../../../integration/tests/counter_test.rs) lines 54-58 fo
For notes with assets and inputs:
```rust
use miden_client::note::NoteAssets;
use miden_standards::notes::FungibleAsset;
use miden_client::asset::FungibleAsset;

let note_assets = NoteAssets::new(vec![FungibleAsset::new(faucet.id(), 50)?.into()])?;
let note = create_testing_note_from_package(
Expand Down Expand Up @@ -115,6 +115,9 @@ mock_chain.prove_next_block()?;
See [counter_test.rs](../../../integration/tests/counter_test.rs) lines 82-92 for reading a StorageMap value and asserting on the result.

### 11. Verify Output Notes

**Important**: `add_output_note()` is only available on `MockChainBuilder` (before `build()`) — use it to seed the chain with existing notes. To verify output notes from a transaction, use `extend_expected_output_notes()` on `TxContextBuilder`:

```rust
use miden_client::note::{Note, NoteAssets, NoteMetadata, NoteRecipient};

Expand All @@ -129,42 +132,34 @@ let tx_context = mock_chain
let executed = tx_context.execute().await?;
```

## Multi-Step Test Pattern
## Multi-Transaction Test Pattern

For contracts requiring initialization before use:
For contracts requiring initialization before use, each step needs its own execute → `apply_delta()` → `add_pending_executed_transaction()` → `prove_next_block()` cycle.

```rust
#[tokio::test]
async fn multi_step_test() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
// ... setup ...
let mut mock_chain = builder.build()?;

// Step 1: Initialize (via tx script)
let init_tx_context = mock_chain
.build_tx_context(account.id(), &[], &[])?
.tx_script(init_script)
.build()?;
let executed_init = init_tx_context.execute().await?;
account.apply_delta(executed_init.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_init)?;
mock_chain.prove_next_block()?;

// Step 2: Main operation (via note consumption)
let tx_context = mock_chain
.build_tx_context(account.id(), &[note.id()], &[])?
.build()?;
let executed = tx_context.execute().await?;
account.apply_delta(executed.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed)?;
mock_chain.prove_next_block()?;

// Step 3: Verify state
// ...

Ok(())
}
```
See [miden-bank withdraw_test.rs](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/integration/tests/withdraw_test.rs) for a complete multi-transaction test demonstrating: initialize bank → deposit assets → withdraw assets (3 sequential transactions with state verification between each step).

See [miden-bank deposit_test.rs](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/integration/tests/deposit_test.rs) for asset-bearing note construction using `NoteAssets::new()` with `FungibleAsset`.

## MockChain Block Numbering

Genesis is block 0. Each `prove_next_block()` advances the block number by 1. In contract code, `tx::get_block_number()` returns the **reference block** — the last proven block at the time the transaction started, not the block the transaction will be included in.

## Note Construction

Always use `create_testing_note_from_package` (or mirror its logic with `.masp` package files) for creating notes in tests. Manually constructed notes may fail with a "private notes cannot be converted" error. See [counter_test.rs](../../../integration/tests/counter_test.rs) for the working pattern.

## Asset-Bearing Note Example

To create a note that carries fungible assets in tests:

1. Create a `FungibleAsset` from a faucet ID and amount.
2. Wrap it in `NoteAssets::new(vec![Asset::Fungible(fungible_asset)])`.
3. Pass the `NoteAssets` into `NoteCreationConfig { assets: note_assets, ..Default::default() }`.
4. Use `create_testing_note_from_package` as usual.

The faucet must be set up first (see Step 3) and the sender wallet must hold sufficient assets (see Step 2).

See [miden-bank deposit_test.rs](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/integration/tests/deposit_test.rs) lines 56-70 for the complete working pattern, including `FungibleAsset::new()`, `NoteAssets::new()`, and `NoteCreationConfig` usage.

## Key Dependencies

Expand All @@ -177,5 +172,5 @@ See [integration/Cargo.toml](../../../integration/Cargo.toml) for the current de
- [ ] All contracts built before account/note creation
- [ ] `apply_delta()` called after each `execute()`
- [ ] `prove_next_block()` called after `add_pending_executed_transaction()`
- [ ] Notes added to builder via `add_output_note(OutputNote::Full(...))`
- [ ] Notes added to `MockChainBuilder` via `add_output_note(OutputNote::Full(...))` (before `build()`)
- [ ] Faucet set up before creating assets
Loading