From 3e5b0509e8b882c990ba404c6bdfdecadca0d89d Mon Sep 17 00:00:00 2001 From: keinberger Date: Mon, 9 Mar 2026 13:15:13 +0200 Subject: [PATCH] docs: remove local miden-bank files (now ingested from miden-tutorials) --- .github/workflows/cut-versions.yml | 6 +- .github/workflows/deploy-docs.yml | 3 +- docs/builder/smart-contracts/index.md | 2 +- docs/builder/smart-contracts/overview.md | 4 +- docs/builder/smart-contracts/patterns.md | 2 +- docs/builder/tutorials/rust-compiler/index.md | 2 +- .../miden-bank/00-project-setup.md | 348 --------- .../miden-bank/01-account-components.md | 426 ----------- .../miden-bank/02-constants-constraints.md | 456 ----------- .../miden-bank/03-asset-management.md | 619 --------------- .../miden-bank/04-note-scripts.md | 534 ------------- .../miden-bank/05-cross-component-calls.md | 290 ------- .../miden-bank/06-transaction-scripts.md | 488 ------------ .../miden-bank/07-output-notes.md | 724 ------------------ .../miden-bank/08-complete-flows.md | 585 -------------- .../rust-compiler/miden-bank/_category_.json | 8 - .../rust-compiler/miden-bank/index.md | 207 ----- .../tutorials/rust-compiler/pitfalls.md | 2 +- .../tutorials/rust-compiler/testing.md | 2 +- docusaurus.config.ts | 4 + 20 files changed, 17 insertions(+), 4695 deletions(-) delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/00-project-setup.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/01-account-components.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/02-constants-constraints.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/03-asset-management.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/04-note-scripts.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/05-cross-component-calls.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/06-transaction-scripts.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/07-output-notes.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/08-complete-flows.md delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/_category_.json delete mode 100644 docs/builder/tutorials/rust-compiler/miden-bank/index.md diff --git a/.github/workflows/cut-versions.yml b/.github/workflows/cut-versions.yml index edc66850..4e739b13 100644 --- a/.github/workflows/cut-versions.yml +++ b/.github/workflows/cut-versions.yml @@ -152,7 +152,8 @@ jobs: # Clean directories that will be re-synced (v0.4 nested paths) rm -rf docs/core-concepts/miden-base docs/core-concepts/miden-vm docs/core-concepts/miden-node docs/core-concepts/compiler rm -rf docs/builder/tools/client - # Note: docs/builder/tutorials is NOT fully cleaned to preserve local tutorials (e.g. miden-bank) + rm -rf docs/builder/tutorials/miden-bank docs/builder/tutorials/index.md + # Note: docs/builder/tutorials/rust-compiler/ is NOT cleaned to preserve local guides (testing, debugging, pitfalls) # Core Concepts docs → docs/core-concepts/* if [ -d "vendor/miden-base/docs/src" ]; then @@ -220,7 +221,8 @@ jobs: rm -rf docs/core-concepts/miden-node rm -rf docs/core-concepts/compiler rm -rf docs/builder/tools/client - # Note: tutorials live in docs/builder/tutorials/ (authored content, not cleaned) + rm -rf docs/builder/tutorials/miden-bank docs/builder/tutorials/index.md + # Note: docs/builder/tutorials/rust-compiler/ is authored content (testing, debugging, pitfalls), not cleaned - name: Commit snapshots run: | diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index afd3ac04..064cf290 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -148,7 +148,8 @@ jobs: # Clean directories that will be re-synced (v0.4 nested paths) rm -rf docs/core-concepts/miden-base docs/core-concepts/miden-vm docs/core-concepts/miden-node docs/core-concepts/compiler rm -rf docs/builder/tools/client - # Note: docs/builder/tutorials is NOT fully cleaned to preserve local tutorials (e.g. miden-bank) + rm -rf docs/builder/tutorials/miden-bank docs/builder/tutorials/index.md + # Note: docs/builder/tutorials/rust-compiler/ is NOT cleaned to preserve local guides (testing, debugging, pitfalls) # Core Concepts docs → docs/core-concepts/* if [ -d "vendor/miden-base/docs/src" ]; then diff --git a/docs/builder/smart-contracts/index.md b/docs/builder/smart-contracts/index.md index 6481d4ec..e119896d 100644 --- a/docs/builder/smart-contracts/index.md +++ b/docs/builder/smart-contracts/index.md @@ -6,7 +6,7 @@ description: "Reference documentation for building Miden smart contracts in Rust # Miden Smart Contracts -This section is the complete reference for building smart contracts on Miden using Rust and the Miden SDK (v0.10). If you're new to Miden, follow the hands-on [Miden Bank Tutorial](../tutorials/rust-compiler/miden-bank/). +This section is the complete reference for building smart contracts on Miden using Rust and the Miden SDK (v0.10). If you're new to Miden, follow the hands-on [Miden Bank Tutorial](../tutorials/miden-bank/). All Miden Rust contracts compile under these constraints: `#![no_std]`, Rust 2024 edition. diff --git a/docs/builder/smart-contracts/overview.md b/docs/builder/smart-contracts/overview.md index f0d787f8..69b7b825 100644 --- a/docs/builder/smart-contracts/overview.md +++ b/docs/builder/smart-contracts/overview.md @@ -6,7 +6,7 @@ description: "Miden's execution model, account structure, note system, and trans # What is a Miden Smart Contract -Miden is a ZK rollup where transactions execute on the client and only a cryptographic proof is submitted to the network. Every entity — wallets, contracts, faucets — is an account with code, storage, a vault, and a nonce. Assets move between accounts through notes, which act as programmable UTXOs. This page describes the execution model, account structure, note system, and transaction lifecycle. For a hands-on walkthrough, see the [Miden Bank Tutorial](../tutorials/rust-compiler/miden-bank/). +Miden is a ZK rollup where transactions execute on the client and only a cryptographic proof is submitted to the network. Every entity — wallets, contracts, faucets — is an account with code, storage, a vault, and a nonce. Assets move between accounts through notes, which act as programmable UTXOs. This page describes the execution model, account structure, note system, and transaction lifecycle. For a hands-on walkthrough, see the [Miden Bank Tutorial](../tutorials/miden-bank/). ## What makes Miden different @@ -148,4 +148,4 @@ Miden supports several account types, configured in `Cargo.toml`: | [Cross-Component Calls](./cross-component-calls) | Inter-component communication | WIT bindings, `generate!()` | | [Types](./types) | Felt, Word, Asset — the VM's native types | Field arithmetic | -Ready to start building? Follow the [Miden Bank Tutorial](../tutorials/rust-compiler/miden-bank/) for a hands-on walkthrough. +Ready to start building? Follow the [Miden Bank Tutorial](../tutorials/miden-bank/) for a hands-on walkthrough. diff --git a/docs/builder/smart-contracts/patterns.md b/docs/builder/smart-contracts/patterns.md index 66bcd57b..bbeb84c0 100644 --- a/docs/builder/smart-contracts/patterns.md +++ b/docs/builder/smart-contracts/patterns.md @@ -6,7 +6,7 @@ description: "Common patterns and security considerations for Miden smart contra # Patterns -Security considerations and common patterns for Miden smart contracts. For runnable examples, see the [compiler examples directory](https://github.com/0xMiden/compiler/tree/next/examples) and the [Miden Bank Tutorial](../tutorials/rust-compiler/miden-bank/). +Security considerations and common patterns for Miden smart contracts. For runnable examples, see the [compiler examples directory](https://github.com/0xMiden/compiler/tree/next/examples) and the [Miden Bank Tutorial](../tutorials/miden-bank/). ## Access control diff --git a/docs/builder/tutorials/rust-compiler/index.md b/docs/builder/tutorials/rust-compiler/index.md index 2243a4d9..f8eea9ed 100644 --- a/docs/builder/tutorials/rust-compiler/index.md +++ b/docs/builder/tutorials/rust-compiler/index.md @@ -29,7 +29,7 @@ If you're new to the Miden Rust compiler, start with the **Miden Bank Tutorial**
- Start Tutorial + Start Tutorial
diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/00-project-setup.md b/docs/builder/tutorials/rust-compiler/miden-bank/00-project-setup.md deleted file mode 100644 index 2ee30806..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/00-project-setup.md +++ /dev/null @@ -1,348 +0,0 @@ ---- -sidebar_position: 0 -title: "Part 0: Project Setup" -description: "Set up a new Miden project and prepare the workspace for building the banking application." ---- - -# Part 0: Project Setup - -In this section, you'll create a new Miden project and set up the workspace structure for our banking application. By the end, you'll have a working project that compiles successfully. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Created a new Miden project using `miden new` -- Understood the workspace structure -- Renamed and configured the project for our bank -- Successfully compiled a minimal account component - -## Prerequisites - -Before starting, ensure you have completed the [Get Started installation guide](../../../get-started/setup/installation) and have: - -- **Rust toolchain** installed and configured -- **midenup toolchain** installed with Miden CLI tools (`miden` command available) - -Verify your installation: - -```bash title=">_ Terminal" -miden --version -``` - -
-Expected output - -```text -The Miden toolchain porcelain: - -Environment: -- cargo version: cargo 1.93.0 (083ac5135 2025-12-15). - -Midenup: -- midenup + miden version: 0.1.0. -- active toolchain version: 0.20.3. -- ... -``` - -
- -## Step 1: Create the Project - -Create a new Miden project using the CLI: - -```bash title=">_ Terminal" -miden new miden-bank -cd miden-bank -``` - -This creates a workspace with the following structure: - -```text -miden-bank/ -├── contracts/ # Smart contract code -│ ├── counter-account/ # Example account contract (we'll replace this) -│ └── increment-note/ # Example note script (we'll replace this) -├── integration/ # Tests and deployment scripts -│ ├── src/ -│ │ ├── bin/ # Executable scripts for on-chain interactions -│ │ ├── lib.rs -│ │ └── helpers.rs # Helper functions for tests -│ └── tests/ # Test files -├── Cargo.toml # Workspace root -└── rust-toolchain.toml # Rust toolchain specification -``` - -The project follows Miden's design philosophy: - -- **`contracts/`**: Your smart contract code (account components, note scripts, transaction scripts) -- **`integration/`**: All on-chain interactions, deployment scripts, and tests - -## Step 2: Set Up the Bank Account Contract - -We'll replace the example `counter-account` with our `bank-account`. First, rename the directory: - -```bash title=">_ Terminal" -mv contracts/counter-account contracts/bank-account -``` - -Now update the `Cargo.toml` inside `contracts/bank-account/`: - -```toml title="contracts/bank-account/Cargo.toml" -[package] -name = "bank-account" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -miden = { version = "0.10" } - -[package.metadata.component] -package = "miden:bank-account" - -[package.metadata.miden] -project-kind = "account" -supported-types = ["RegularAccountImmutableCode"] -``` - -### Key Configuration Options - -| Field | Description | -|-------|-------------| -| `crate-type = ["cdylib"]` | Required for WebAssembly compilation | -| `project-kind = "account"` | Tells the compiler this is an account component | -| `supported-types` | Account types this component supports | -| `package = "miden:bank-account"` | The component package name for cross-component calls | - -:::info Supported Account Types -`RegularAccountImmutableCode` means the account code cannot be changed after deployment. This is appropriate for our bank since we want the logic to be fixed. -::: - -## Step 3: Create a Minimal Bank Component - -Replace the contents of `contracts/bank-account/src/lib.rs` with a minimal bank structure: - -```rust title="contracts/bank-account/src/lib.rs" -// Do not link against libstd (i.e. anything defined in `std::`) -#![no_std] -#![feature(alloc_error_handler)] - -#[macro_use] -extern crate alloc; - -use miden::*; - -/// Bank account component - we'll build this up throughout the tutorial. -#[component] -struct Bank { - /// Tracks whether the bank has been initialized (deposits enabled). - /// Word layout: [is_initialized (0 or 1), 0, 0, 0] - #[storage(description = "initialized")] - initialized: Value, - - /// Maps depositor AccountId -> balance (as Felt). - /// We'll use this to track user balances in Part 1. - #[storage(description = "balances")] - balances: StorageMap, -} - -#[component] -impl Bank { - /// Initialize the bank account, enabling deposits. - pub fn initialize(&mut self) { - // Read current value from storage - let current: Word = self.initialized.read(); - - // Check not already initialized - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - // Set initialized flag to 1 - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); - } - - /// Get the balance for a depositor. - /// - /// This method is required for the component to compile correctly - - /// account components must use WIT binding types (like AccountId) - /// in at least one public method. - pub fn get_balance(&self, depositor: AccountId) -> Felt { - let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]); - self.balances.get(&key) - } -} -``` - -This is our starting point with two storage slots: -- `initialized`: A `Value` slot to track whether the bank is ready -- `balances`: A `StorageMap` to track user balances (we'll use this starting in Part 1) - -:::note Compiler Requirement -Account components must use WIT binding types (like `AccountId`, `Asset`, etc.) in at least one public method signature for the compiler to generate the required bindings correctly. The `get_balance` method serves this purpose. -::: - -## Step 4: Update the Workspace Configuration - -Update the root `Cargo.toml` to reflect our renamed contract: - -```toml title="Cargo.toml" -[workspace] -members = [ - "integration" -] -exclude = [ - "contracts/", -] -resolver = "2" - -[workspace.package] -edition = "2021" - -[workspace.dependencies] -``` - -:::info Contracts Are Excluded -In v0.13, contracts are excluded from the Cargo workspace and built independently by `cargo miden`. Each contract specifies its own `miden` dependency directly. Only the `integration` crate remains a workspace member. -::: - -## Step 5: Build and Verify - -Let's verify everything compiles correctly: - -```bash title=">_ Terminal" -cd contracts/bank-account -miden build --release -``` - -
-Expected output - -```text - Compiling bank-account v0.1.0 (/path/to/miden-bank/contracts/bank-account) - Finished `release` profile [optimized] target(s) -Creating Miden package /path/to/miden-bank/target/miden/release/bank_account.masp -``` - -
- -The compiled output is stored in `target/miden/release/bank_account.masp`. - -:::tip What's a .masp File? -A `.masp` file is a Miden Assembly Package. It contains the compiled MASM (Miden Assembly) code and metadata needed to deploy and interact with your contract. -::: - -## Try It: Verify Your Setup - -Let's create a simple test to verify the bank account can be created. Create a new test file: - -```rust title="integration/tests/part0_setup_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, AccountCreationConfig, -}; -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; -use miden_client::Word; -use std::{path::Path, sync::Arc}; - -#[tokio::test] -async fn test_bank_account_builds_and_loads() -> anyhow::Result<()> { - // Build the bank account contract - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - - // Create named storage slots matching the contract's storage layout - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot, Word::default()), - StorageSlot::with_map( - balances_slot, - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - - let bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - // Verify the account was created - println!("Bank account created with ID: {:?}", bank_account.id()); - println!("Part 0 setup verified!"); - - Ok(()) -} -``` - -Run the test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_bank_account_builds_and_loads -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part0_setup_test.rs - -running 1 test -Bank account created with ID: 0x... -Part 0 setup verified! -test test_bank_account_builds_and_loads ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -:::tip Troubleshooting -**"Failed to build bank account contract"**: Make sure the `contracts/bank-account/Cargo.toml` is properly configured and you've updated the root `Cargo.toml` members list. - -**"cannot find module helpers"**: Ensure the `integration/src/helpers.rs` file exists (it should have been generated by `miden new`). -::: - -## What We've Built So Far - -At this point, you have: - -| Component | Status | Description | -|-----------|--------|-------------| -| `bank-account` | Minimal | Initialization flag + balance storage | -| `deposit-note` | Not started | Coming in Part 4 | -| `withdraw-note` | Not started | Coming in Part 7 | -| `init-tx-script` | Not started | Coming in Part 6 | - -Your bank can be created, but doesn't do anything useful yet. In the next parts, we'll add: - -1. **Part 1**: Deeper dive into storage (Value vs StorageMap) -2. **Part 2**: Business rules and constraints -3. **Part 3**: Asset handling for deposits -4. And more... - -## Key Takeaways - -1. **`miden new`** creates a complete project workspace with contracts and integration folders -2. **Account components** are defined with `#[component]` on a struct -3. **Storage slots** are declared with `#[storage(description = "...")]` attributes (the compiler auto-assigns slot numbers) -4. **`miden build`** compiles Rust to Miden Assembly (.masp package) -5. **Tests verify** that your code works before moving on - -## Next Steps - -Now that your project is set up, let's dive deeper into account components and storage in [Part 1: Account Components and Storage](./account-components). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/01-account-components.md b/docs/builder/tutorials/rust-compiler/miden-bank/01-account-components.md deleted file mode 100644 index 3bda45a0..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/01-account-components.md +++ /dev/null @@ -1,426 +0,0 @@ ---- -sidebar_position: 1 -title: "Part 1: Account Components and Storage" -description: "Learn how to define account components with the #[component] attribute and manage persistent state using Value and StorageMap storage types." ---- - -# Part 1: Account Components and Storage - -In this section, you'll learn the fundamentals of building Miden account components. We'll expand our Bank to include balance tracking with a `StorageMap`, giving us the foundation for deposits and withdrawals. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Understood the `#[component]` attribute and what it generates -- Added a `StorageMap` for tracking depositor balances -- Implemented a `get_balance()` query method -- **Verified it works** with a MockChain test - -## Building on Part 0 - -In Part 0, we created a minimal bank with just an `initialized` flag. Now we'll add balance tracking: - -```text -Part 0: Part 1: -┌────────────────────┐ ┌──────────────────────────┐ -│ Bank │ │ Bank │ -│ ───────────────── │ ──► │ ──────────────────────── │ -│ initialized (Value)│ │ initialized (Value) │ -│ │ │ balances (StorageMap) │ ◄── NEW -└────────────────────┘ └──────────────────────────┘ -``` - -## The #[component] Attribute - -The `#[component]` attribute marks a struct as a Miden account component. When you compile with `miden build`, it generates: - -- **WIT (WebAssembly Interface Types)** bindings for cross-component calls -- **MASM (Miden Assembly)** code for the account logic -- **Storage slot management** code - -Let's expand our Bank component: - -## Step 1: Add the Balances Storage Map - -Update `contracts/bank-account/src/lib.rs`: - -```rust title="contracts/bank-account/src/lib.rs" {17-20} -#![no_std] -#![feature(alloc_error_handler)] - -#[macro_use] -extern crate alloc; - -use miden::*; - -/// Bank account component that tracks depositor balances. -#[component] -struct Bank { - /// Tracks whether the bank has been initialized (deposits enabled). - /// Word layout: [is_initialized (0 or 1), 0, 0, 0] - #[storage(description = "initialized")] - initialized: Value, - - /// Maps depositor AccountId -> balance (as Felt) - /// Key: [prefix, suffix, asset_prefix, asset_suffix] - #[storage(description = "balances")] - balances: StorageMap, -} -``` - -We've added a `StorageMap` that will track each depositor's balance. The compiler auto-assigns slot numbers based on field order. - -## Storage Types Explained - -Miden accounts have storage slots that persist state on-chain. Each slot holds one `Word` (4 Felts = 32 bytes). The Miden Rust compiler provides two abstractions: - -### Value Storage - -The `Value` type provides access to a single storage slot: - -```rust -#[storage(description = "initialized")] -initialized: Value, -``` - -Use `Value` when you need to store a single `Word` of data. - -**Reading and writing:** - -```rust -// Read returns a Word -let current: Word = self.initialized.read(); - -// Check the first element (our flag) -if current[0].as_u64() == 0 { - // Not initialized -} - -// Write a new value -let new_value = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); -self.initialized.write(new_value); -``` - -:::tip Type Annotations -The `.read()` method requires a type annotation: `let current: Word = self.initialized.read();` -::: - -### StorageMap - -The `StorageMap` type provides key-value storage within a slot: - -```rust -#[storage(description = "balances")] -balances: StorageMap, -``` - -Use `StorageMap` when you need to store multiple values indexed by keys. - -**Reading and writing:** - -```rust -// Create a key (must be a Word) -let key = Word::from([ - depositor.prefix, - depositor.suffix, - felt!(0), - felt!(0), -]); - -// Get returns a Felt (single value, not a Word) -let balance: Felt = self.balances.get(&key); - -// Set stores a Felt at the key -let new_balance = balance + deposit_amount; -self.balances.set(key, new_balance); -``` - -:::warning StorageMap Returns Felt -Unlike `Value::read()` which returns a `Word`, `StorageMap::get()` returns a single `Felt`. This is an important distinction. -::: - -### Storage Layout - -Plan your storage layout carefully: - -| Name | Type | Purpose | -|------|------|---------| -| `initialized` | `Value` | Initialization flag | -| `balances` | `StorageMap` | Depositor balances | - -The `description` attribute generates named slot identifiers (e.g., `miden::component::miden_bank_account::initialized`) used in tests to reference specific slots. The compiler auto-assigns slot numbers based on field order. - -## Step 2: Implement Component Methods - -Now let's add methods to our Bank. The `#[component]` attribute is also used on the `impl` block: - -```rust title="contracts/bank-account/src/lib.rs" -#[component] -impl Bank { - /// Initialize the bank account, enabling deposits. - pub fn initialize(&mut self) { - // Read current value from storage - let current: Word = self.initialized.read(); - - // Check not already initialized - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - // Set initialized flag to 1 - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); - } - - /// Get the balance for a depositor. - pub fn get_balance(&self, depositor: AccountId) -> Felt { - let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]); - self.balances.get(&key) - } - - /// Check that the bank is initialized. - fn require_initialized(&self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 1, - "Bank not initialized - deposits not enabled" - ); - } -} -``` - -### Public vs Private Methods - -- **Public methods** (`pub fn`) are exposed in the generated WIT interface and can be called by other contracts -- **Private methods** (`fn`) are internal and cannot be called from outside - -```rust -// Public: Can be called by note scripts and other contracts -pub fn get_balance(&self, depositor: AccountId) -> Felt { ... } - -// Private: Internal helper, not exposed -fn require_initialized(&self) { ... } -``` - -## Step 3: Build the Component - -Build your updated account component: - -```bash title=">_ Terminal" -cd contracts/bank-account -miden build -``` - -This compiles the Rust code to Miden Assembly and generates: - -- `target/miden/release/bank_account.masp` - The compiled package -- `target/generated-wit/` - WIT interface files for other contracts to use - -## Try It: Verify Your Code - -Let's write a MockChain test to verify our Bank component works correctly. This test will: -1. Create a bank account -2. Initialize it -3. Verify the storage was updated - -Create a new test file: - -```rust title="integration/tests/part1_account_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, AccountCreationConfig, -}; -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; -use miden_client::{Felt, Word}; -use std::{path::Path, sync::Arc}; - -#[tokio::test] -async fn test_bank_account_storage() -> anyhow::Result<()> { - // ========================================================================= - // SETUP: Build contracts and create the bank account - // ========================================================================= - - // Build the bank account contract - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - - // Create named storage slots matching the contract's storage layout - // The naming convention is: miden::component::{package_name_underscored}::{field_name} - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot.clone(), Word::default()), - StorageSlot::with_map( - balances_slot.clone(), - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - - let bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - // ========================================================================= - // VERIFY: Check initial storage state - // ========================================================================= - - // Verify initialized flag starts as 0 - let initialized_value = bank_account.storage().get_item(&initialized_slot)?; - assert_eq!( - initialized_value, - Word::default(), - "Initialized flag should start as 0" - ); - - println!("Bank account created successfully!"); - println!(" Account ID: {:?}", bank_account.id()); - println!(" Initialized flag: {:?}", initialized_value[0].as_int()); - - // ========================================================================= - // VERIFY: Storage slots are correctly configured - // ========================================================================= - - // Check that we can query the balances map (should return 0 for any key) - let test_key = Word::from([Felt::new(1), Felt::new(2), Felt::new(0), Felt::new(0)]); - let balance = bank_account.storage().get_map_item(&balances_slot, test_key)?; - - // Balance for non-existent depositor should be all zeros - assert_eq!( - balance, - Word::default(), - "Balance for unknown depositor should be zero" - ); - - println!(" Balances map accessible: Yes"); - println!("\nPart 1 test passed!"); - - Ok(()) -} -``` - -Run the test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_bank_account_storage -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part1_account_test.rs - -running 1 test -Bank account created successfully! - Account ID: 0x... - Initialized flag: 0 - Balances map accessible: Yes - -Part 1 test passed! -test test_bank_account_storage ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -:::tip Troubleshooting -**"cannot find function `build_project_in_dir`"**: Make sure your `integration/src/helpers.rs` exports this function and `integration/src/lib.rs` has `pub mod helpers;`. - -**"StorageSlot not found"**: Ensure you're using the correct imports: `use miden_client::account::{StorageSlot, StorageSlotName};` -::: - -## Complete Code for This Part - -Here's the full `lib.rs` after Part 1: - -
-Click to expand full code - -```rust title="contracts/bank-account/src/lib.rs" -#![no_std] -#![feature(alloc_error_handler)] - -#[macro_use] -extern crate alloc; - -use miden::*; - -/// Bank account component that tracks depositor balances. -#[component] -struct Bank { - /// Tracks whether the bank has been initialized (deposits enabled). - /// Word layout: [is_initialized (0 or 1), 0, 0, 0] - #[storage(description = "initialized")] - initialized: Value, - - /// Maps depositor AccountId -> balance (as Felt) - /// Key: [prefix, suffix, asset_prefix, asset_suffix] - #[storage(description = "balances")] - balances: StorageMap, -} - -#[component] -impl Bank { - /// Initialize the bank account, enabling deposits. - pub fn initialize(&mut self) { - // Read current value from storage - let current: Word = self.initialized.read(); - - // Check not already initialized - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - // Set initialized flag to 1 - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); - } - - /// Get the balance for a depositor. - pub fn get_balance(&self, depositor: AccountId) -> Felt { - let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]); - self.balances.get(&key) - } - - /// Check that the bank is initialized. - fn require_initialized(&self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 1, - "Bank not initialized - deposits not enabled" - ); - } -} -``` - -
- -## Key Takeaways - -1. **`#[component]`** marks structs and impl blocks as Miden account components -2. **`Value`** stores a single Word, read with `.read()`, write with `.write()` -3. **`StorageMap`** stores key-value pairs, access with `.get()` and `.set()` -4. **Storage slots** are identified by name (auto-assigned by compiler), each holds 4 Felts (32 bytes) -5. **Public methods** are callable by other contracts via generated bindings - -:::tip View Complete Source -See the complete bank account implementation in the [miden-bank repository](https://github.com/keinberger/miden-bank/blob/main/contracts/bank-account/src/lib.rs). -::: - -## Next Steps - -Now that you understand account components and storage, let's learn how to define business rules with [Part 2: Constants and Constraints](./constants-constraints). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/02-constants-constraints.md b/docs/builder/tutorials/rust-compiler/miden-bank/02-constants-constraints.md deleted file mode 100644 index 2e47aeee..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/02-constants-constraints.md +++ /dev/null @@ -1,456 +0,0 @@ ---- -sidebar_position: 2 -title: "Part 2: Constants and Constraints" -description: "Learn how to define constants for business rules and use assertions to validate transactions in Miden Rust contracts." ---- - -# Part 2: Constants and Constraints - -In this section, you'll learn how to define business rules using constants and enforce them with assertions. We'll implement deposit limits and see how failed constraints cause transactions to be rejected. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Defined constants for business rules -- Used `assert!()` for transaction validation -- Learned safe Felt comparison with `.as_u64()` -- Added a deposit method skeleton with validation -- **Verified constraints work** by testing that invalid operations fail - -## Building on Part 1 - -In Part 1, we set up the Bank's storage structure. Now we'll add business rules: - -```text -Part 1: Part 2: -┌──────────────────┐ ┌──────────────────┐ -│ Bank │ │ Bank │ -│ ─────────────────│ ──► │ ─────────────────│ -│ + initialize() │ │ + initialize() │ -│ + get_balance() │ │ + get_balance() │ -│ │ │ + deposit() │ ◄── NEW (skeleton) -│ │ │ + MAX_DEPOSIT │ ◄── NEW constant -└──────────────────┘ └──────────────────┘ -``` - -## Defining Constants - -Constants in Miden Rust contracts work just like regular Rust constants: - -```rust title="contracts/bank-account/src/lib.rs" -/// Maximum allowed deposit amount per transaction. -/// -/// Value: 1,000,000 tokens (arbitrary limit for demonstration) -const MAX_DEPOSIT_AMOUNT: u64 = 1_000_000; -``` - -Use constants for: - -- Business rule limits (max amounts, timeouts) -- Magic numbers that need documentation -- Values used in multiple places - -:::info Constants vs Storage -Constants are compiled into the contract code and cannot change. Use storage slots for values that need to be modified at runtime. -::: - -## The assert!() Macro - -The `assert!()` macro validates conditions during transaction execution: - -```rust title="contracts/bank-account/src/lib.rs" -pub fn initialize(&mut self) { - // Check not already initialized - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - // Set initialized flag to 1 - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); -} -``` - -When an assertion fails: - -1. The Miden VM execution halts -2. No valid proof can be generated -3. The transaction is rejected - -This is the primary mechanism for enforcing business rules in Miden contracts. - -## Safe Felt Comparisons - -:::warning Pitfall: Felt Comparison Operators -Never use `<`, `>`, `<=`, or `>=` operators directly on `Felt` values. They produce incorrect results due to field element ordering. -::: - -**Wrong approach:** - -```rust -// DON'T DO THIS - produces incorrect results -if deposit_amount > felt!(1_000_000) { - // This comparison is unreliable! -} -``` - -**Correct approach:** - -```rust -// CORRECT - convert to u64 first -if deposit_amount.as_u64() > MAX_DEPOSIT_AMOUNT { - // This works correctly -} -``` - -The `.as_u64()` method extracts the underlying 64-bit integer from a Felt, allowing standard Rust comparisons. - -## Step 1: Add the Constant and Deposit Method - -Update your `contracts/bank-account/src/lib.rs` to add the constant and a deposit method skeleton: - -```rust title="contracts/bank-account/src/lib.rs" {1-4,36-55} -/// Maximum allowed deposit amount per transaction. -/// -/// Value: 1,000,000 tokens (arbitrary limit for demonstration) -const MAX_DEPOSIT_AMOUNT: u64 = 1_000_000; - -#[component] -impl Bank { - /// Initialize the bank account, enabling deposits. - pub fn initialize(&mut self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); - } - - /// Get the balance for a depositor. - pub fn get_balance(&self, depositor: AccountId) -> Felt { - let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]); - self.balances.get(&key) - } - - /// Check that the bank is initialized. - fn require_initialized(&self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 1, - "Bank not initialized - deposits not enabled" - ); - } - - /// Deposit assets into the bank. - /// For now, this just validates constraints - we'll add asset handling in Part 3. - pub fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) { - // ======================================================================== - // CONSTRAINT: Bank must be initialized - // ======================================================================== - self.require_initialized(); - - // Extract the fungible amount from the asset - let deposit_amount = deposit_asset.inner[0]; - - // ======================================================================== - // CONSTRAINT: Maximum deposit amount check - // ======================================================================== - assert!( - deposit_amount.as_u64() <= MAX_DEPOSIT_AMOUNT, - "Deposit amount exceeds maximum allowed" - ); - - // We'll add balance tracking and asset handling in Part 3 - // For now, just validate the constraints - } -} -``` - -### The require_initialized() Guard - -We use a helper method to check initialization state: - -```rust -fn require_initialized(&self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 1, - "Bank not initialized - deposits not enabled" - ); -} -``` - -This pattern: - -- Centralizes the initialization check -- Provides a clear error message -- Can be reused across multiple methods - -## How Assertions Affect Proving - -When an assertion fails in the Miden VM: - -```text -Transaction Execution Flow: -┌─────────────────────┐ -│ User submits TX │ -└──────────┬──────────┘ - ▼ -┌─────────────────────┐ -│ VM executes code │ -└──────────┬──────────┘ - ▼ - ┌──────┴──────┐ - │ Assertion? │ - └──────┬──────┘ - Pass │ Fail - ┌──────┴──────┐ - ▼ ▼ -┌────────┐ ┌────────────┐ -│ Prove │ │ TX Rejected│ -│ Success│ │ No Proof │ -└────────┘ └────────────┘ -``` - -Key points: - -- Failed assertions prevent proof generation -- No state changes occur if the transaction fails -- Error messages help with debugging - -## Step 2: Build and Verify - -Build the updated contract: - -```bash title=">_ Terminal" -cd contracts/bank-account -miden build -``` - -## Try It: Verify Constraints Work - -Let's write a test to verify our constraints work correctly. This test verifies that depositing without initialization fails: - -```rust title="integration/tests/part2_constraints_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, AccountCreationConfig, -}; -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; -use miden_client::Word; -use std::{path::Path, sync::Arc}; - -/// Test that our constraint logic is set up correctly -#[tokio::test] -async fn test_constraints_are_defined() -> anyhow::Result<()> { - // Build the bank account contract to verify it compiles with constraints - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - - // Create named storage slots - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - // Create an uninitialized bank account - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot.clone(), Word::default()), - StorageSlot::with_map( - balances_slot, - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - - let bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - // Verify the bank starts uninitialized - let initialized = bank_account.storage().get_item(&initialized_slot)?; - assert_eq!( - initialized[0].as_int(), - 0, - "Bank should start uninitialized" - ); - - println!("Bank account created with constraints!"); - println!(" - MAX_DEPOSIT_AMOUNT: 1,000,000"); - println!(" - require_initialized() guard in place"); - println!(" - Initialization status: {}", initialized[0].as_int()); - println!("\nPart 2 constraints test passed!"); - - Ok(()) -} -``` - -Run the test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_constraints_are_defined -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part2_constraints_test.rs - -running 1 test -Bank account created with constraints! - - MAX_DEPOSIT_AMOUNT: 1,000,000 - - require_initialized() guard in place - - Initialization status: 0 - -Part 2 constraints test passed! -test test_constraints_are_defined ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -:::tip Preview: Testing Failed Assertions -In Part 4, when we have the deposit note script, we'll write a full test that verifies: -1. Depositing without initialization fails -2. Depositing amounts over MAX_DEPOSIT_AMOUNT fails - -For now, the constraint logic is in place and we've verified the contract compiles. -::: - -## Common Constraint Patterns - -### Balance Checks (Preview for Part 3) - -```rust -fn require_sufficient_balance(&self, depositor: AccountId, amount: Felt) { - let balance = self.get_balance(depositor); - assert!( - balance.as_u64() >= amount.as_u64(), - "Insufficient balance" - ); -} -``` - -:::danger Critical: Always Validate Before Subtraction -This pattern is **mandatory** for any operation that subtracts from a balance. Miden uses field element (Felt) arithmetic, which is modular. Without this check, subtracting more than the balance would NOT cause an error - instead, the value would silently wrap around to a large positive number, effectively allowing unlimited withdrawals. See [Common Pitfalls](../pitfalls#felt-arithmetic-underflowoverflow) for more details. -::: - -### State Checks - -```rust -fn require_not_paused(&self) { - let paused: Word = self.paused.read(); - assert!( - paused[0].as_u64() == 0, - "Contract is paused" - ); -} -``` - -## Complete Code for This Part - -Here's the full `lib.rs` after Part 2: - -
-Click to expand full code - -```rust title="contracts/bank-account/src/lib.rs" -#![no_std] -#![feature(alloc_error_handler)] - -#[macro_use] -extern crate alloc; - -use miden::*; - -/// Maximum allowed deposit amount per transaction. -const MAX_DEPOSIT_AMOUNT: u64 = 1_000_000; - -/// Bank account component that tracks depositor balances. -#[component] -struct Bank { - #[storage(description = "initialized")] - initialized: Value, - - #[storage(description = "balances")] - balances: StorageMap, -} - -#[component] -impl Bank { - /// Initialize the bank account, enabling deposits. - pub fn initialize(&mut self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); - } - - /// Get the balance for a depositor. - pub fn get_balance(&self, depositor: AccountId) -> Felt { - let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]); - self.balances.get(&key) - } - - /// Check that the bank is initialized. - fn require_initialized(&self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 1, - "Bank not initialized - deposits not enabled" - ); - } - - /// Deposit assets into the bank. - pub fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) { - // CONSTRAINT: Bank must be initialized - self.require_initialized(); - - let deposit_amount = deposit_asset.inner[0]; - - // CONSTRAINT: Maximum deposit amount check - assert!( - deposit_amount.as_u64() <= MAX_DEPOSIT_AMOUNT, - "Deposit amount exceeds maximum allowed" - ); - - // Balance tracking and asset handling added in Part 3 - } -} -``` - -
- -## Key Takeaways - -1. **Constants** define immutable business rules at compile time -2. **`assert!()`** enforces constraints - failures reject the transaction -3. **Always use `.as_u64()`** for Felt comparisons, never direct operators -4. **Helper methods** like `require_initialized()` centralize validation logic -5. **Failed assertions** mean no valid proof can be generated - -:::tip View Complete Source -See the complete constraint implementation in the [miden-bank repository](https://github.com/keinberger/miden-bank/blob/main/contracts/bank-account/src/lib.rs). -::: - -## Next Steps - -Now that you can define and enforce business rules, let's learn how to handle assets in [Part 3: Asset Management](./asset-management). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/03-asset-management.md b/docs/builder/tutorials/rust-compiler/miden-bank/03-asset-management.md deleted file mode 100644 index b6bdce3b..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/03-asset-management.md +++ /dev/null @@ -1,619 +0,0 @@ ---- -sidebar_position: 3 -title: "Part 3: Asset Management" -description: "Learn how to handle fungible assets in Miden Rust contracts using vault operations and balance tracking." ---- - -# Part 3: Asset Management - -In this section, you'll learn how to receive and send assets in Miden accounts. We'll complete the deposit logic that receives tokens into the bank's vault and tracks balances per depositor. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Understood the `Asset` type structure for fungible assets -- Implemented full deposit logic with `native_account::add_asset()` -- Learned about balance key design for per-user, per-asset tracking -- Added a withdraw method skeleton (to be completed in Part 7) -- **Verified deposits work** with a MockChain test - -## Building on Part 2 - -In Part 2, we added constraints. Now we'll complete the deposit function with actual asset handling: - -```text -Part 2: Part 3: -┌──────────────────┐ ┌──────────────────┐ -│ Bank │ │ Bank │ -│ ─────────────────│ ──► │ ─────────────────│ -│ + deposit() │ │ + deposit() │ ◄── COMPLETE -│ (skeleton) │ │ + balance tracking -│ │ │ + vault operations -│ │ │ + withdraw() │ ◄── NEW (skeleton) -└──────────────────┘ └──────────────────┘ -``` - -## The Asset Type - -Miden represents fungible assets as a `Word` (4 Felts) with this layout: - -```text -Asset Layout: [amount, 0, faucet_suffix, faucet_prefix] - ━━━━━━━ ━ ━━━━━━━━━━━━━ ━━━━━━━━━━━━━ - index 0 1 index 2 index 3 -``` - -| Index | Field | Description | -| ----- | --------------- | ------------------------------------ | -| 0 | `amount` | The quantity of tokens | -| 1 | (reserved) | Always 0 for fungible assets | -| 2 | `faucet_suffix` | Second part of the faucet account ID | -| 3 | `faucet_prefix` | First part of the faucet account ID | - -Access these fields through `asset.inner`: - -```rust -let amount = deposit_asset.inner[0]; // The token amount -let faucet_suffix = deposit_asset.inner[2]; // Faucet ID suffix -let faucet_prefix = deposit_asset.inner[3]; // Faucet ID prefix -``` - -## Receiving Assets with add_asset() - -The `native_account::add_asset()` function adds an asset to the account's vault: - -```rust -// Add asset to the bank's vault -native_account::add_asset(deposit_asset); -``` - -When called: - -- The asset is added to the account's internal vault -- The vault tracks all assets the account holds -- Multiple assets of the same type are combined automatically - -:::info Vault vs Balance Tracking -The vault is managed by the Miden protocol automatically. Our `StorageMap` for balances is an **application-level** tracking of who deposited what, separate from the protocol-level vault. -::: - -## Step 1: Complete the Deposit Function - -Update `contracts/bank-account/src/lib.rs` to complete the deposit function with balance tracking and vault operations: - -```rust title="contracts/bank-account/src/lib.rs" -/// Deposit assets into the bank. -pub fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) { - // ======================================================================== - // CONSTRAINT: Bank must be initialized - // ======================================================================== - self.require_initialized(); - - // Extract the fungible amount from the asset - let deposit_amount = deposit_asset.inner[0]; - - // ======================================================================== - // CONSTRAINT: Maximum deposit amount check - // ======================================================================== - assert!( - deposit_amount.as_u64() <= MAX_DEPOSIT_AMOUNT, - "Deposit amount exceeds maximum allowed" - ); - - // ======================================================================== - // UPDATE BALANCE - // ======================================================================== - // Create key from depositor's AccountId and asset faucet ID - // This allows tracking balances per depositor per asset type - let key = Word::from([ - depositor.prefix, - depositor.suffix, - deposit_asset.inner[3], // asset prefix (faucet) - deposit_asset.inner[2], // asset suffix (faucet) - ]); - - // Update balance: current + deposit_amount - let current_balance: Felt = self.balances.get(&key); - let new_balance = current_balance + deposit_amount; - self.balances.set(key, new_balance); - - // ======================================================================== - // ADD ASSET TO VAULT - // ======================================================================== - native_account::add_asset(deposit_asset); -} -``` - -### Balance Key Design - -We construct a composite key for balance tracking: - -```rust -let key = Word::from([ - depositor.prefix, // Who deposited - depositor.suffix, - deposit_asset.inner[3], // Which asset type (faucet ID prefix) - deposit_asset.inner[2], // Which asset type (faucet ID suffix) -]); -``` - -This design allows: - -- **Per-depositor tracking**: Each user has their own balance -- **Per-asset tracking**: Different token types are tracked separately -- **Unique keys**: The combination ensures no collisions - -## Step 2: Add the Withdraw Method Skeleton - -Now add a withdraw method skeleton. We'll complete it in Part 7 when we cover output notes. - -:::danger Critical Security Warning: Felt Arithmetic Underflow - -Miden uses **modular field arithmetic**. Subtracting a larger value from a smaller one does **NOT** cause an error - it **silently wraps** to a massive positive number! - -For example: `50 - 100` does NOT equal `-50`. Instead, it equals a number close to `2^64`. - -**You MUST validate before ANY subtraction:** - -```rust -// WRONG - DANGEROUS! Silent underflow if balance < amount -let new_balance = current_balance - withdraw_amount; - -// CORRECT - Always validate first -assert!( - current_balance.as_u64() >= withdraw_amount.as_u64(), - "Withdrawal amount exceeds available balance" -); -let new_balance = current_balance - withdraw_amount; -``` - -This is not optional - it's a **security requirement** for any financial operation. -::: - -Add this method to your Bank impl block: - -```rust title="contracts/bank-account/src/lib.rs" -/// Withdraw assets from the bank. -/// Creates a P2ID note to send assets back to the depositor. -pub fn withdraw( - &mut self, - depositor: AccountId, - withdraw_asset: Asset, - serial_num: Word, - tag: Felt, - note_type: Felt, -) { - // ======================================================================== - // CONSTRAINT: Bank must be initialized - // ======================================================================== - self.require_initialized(); - - // Extract the fungible amount from the asset - let withdraw_amount = withdraw_asset.inner[0]; - - // Create key from depositor's AccountId and asset faucet ID - let key = Word::from([ - depositor.prefix, - depositor.suffix, - withdraw_asset.inner[3], - withdraw_asset.inner[2], - ]); - - // ======================================================================== - // CRITICAL: Validate balance BEFORE subtraction - // ======================================================================== - // Get current balance and validate sufficient funds exist. - // This check is critical: Felt arithmetic is modular, so subtracting - // more than the balance would silently wrap to a large positive number. - let current_balance: Felt = self.balances.get(&key); - assert!( - current_balance.as_u64() >= withdraw_amount.as_u64(), - "Withdrawal amount exceeds available balance" - ); - - // Now safe to subtract - let new_balance = current_balance - withdraw_amount; - self.balances.set(key, new_balance); - - // Create a P2ID note to send the requested asset back to the depositor - // We'll implement create_p2id_note() in Part 7 - self.create_p2id_note(serial_num, &withdraw_asset, depositor, tag, note_type); -} -``` - -For now, add a placeholder for `create_p2id_note()`: - -```rust title="contracts/bank-account/src/lib.rs" -/// Create a P2ID note to send assets to a recipient. -/// Full implementation in Part 7. -fn create_p2id_note( - &mut self, - _serial_num: Word, - _asset: &Asset, - _recipient_id: AccountId, - _tag: Felt, - _note_type: Felt, -) { - // Placeholder - implemented in Part 7: Output Notes - // For now, this will cause a compile error if actually called - todo!("P2ID note creation - see Part 7") -} -``` - -## Step 3: Build and Verify - -Build the contract: - -```bash title=">_ Terminal" -cd contracts/bank-account -miden build -``` - -## Try It: Verify Deposits Work - -Let's write a test to verify our deposit logic works correctly: - -```rust title="integration/tests/part3_deposit_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, - create_testing_note_from_package, AccountCreationConfig, NoteCreationConfig, -}; -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; -use miden_client::asset::{Asset, FungibleAsset}; -use miden_client::note::NoteAssets; -use miden_client::transaction::{OutputNote, TransactionScript}; -use miden_client::{Felt, Word}; -use miden_testing::{Auth, MockChain}; -use std::{path::Path, sync::Arc}; - -#[tokio::test] -async fn test_deposit_updates_balance() -> anyhow::Result<()> { - // ========================================================================= - // SETUP - // ========================================================================= - let mut builder = MockChain::builder(); - - // Create a faucet for test tokens - let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth, "TEST", 10_000_000, Some(10))?; - - // Create sender wallet with tokens - let sender = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [FungibleAsset::new(faucet.id(), 1000)?.into()])?; - - // Build contracts - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - - let deposit_note_package = Arc::new(build_project_in_dir( - Path::new("../contracts/deposit-note"), - true, - )?); - - let init_tx_script_package = Arc::new(build_project_in_dir( - Path::new("../contracts/init-tx-script"), - true, - )?); - - // Create the bank account with storage slots - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot.clone(), Word::default()), - StorageSlot::with_map( - balances_slot.clone(), - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - - let mut bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - // Add to mock chain - builder.add_account(bank_account.clone())?; - - // ========================================================================= - // STEP 2: Create deposit note before building the mock chain - // ========================================================================= - let deposit_amount: u64 = 1000; - let fungible_asset = FungibleAsset::new(faucet.id(), deposit_amount)?; - let note_assets = NoteAssets::new(vec![Asset::Fungible(fungible_asset)])?; - - let deposit_note = create_testing_note_from_package( - deposit_note_package.clone(), - sender.id(), - NoteCreationConfig { - assets: note_assets, - ..Default::default() - }, - )?; - - // Add note to builder before building - builder.add_output_note(OutputNote::Full(deposit_note.clone())); - - let mut mock_chain = builder.build()?; - - // ========================================================================= - // STEP 1: Initialize the bank - // ========================================================================= - let init_program = init_tx_script_package.unwrap_program(); - let init_tx_script = TransactionScript::new((*init_program).clone()); - - let init_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[], &[])? - .tx_script(init_tx_script) - .build()?; - - let executed_init = init_tx_context.execute().await?; - bank_account.apply_delta(&executed_init.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_init)?; - mock_chain.prove_next_block()?; - - // Verify initialization - let initialized = bank_account.storage().get_item(&initialized_slot)?; - assert_eq!( - initialized[0].as_int(), - 1, - "Bank should be initialized" - ); - println!("Bank initialized successfully!"); - - // ========================================================================= - // STEP 2: Execute deposit - // ========================================================================= - - // Execute deposit transaction - let tx_context = mock_chain - .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])? - .build()?; - - let executed_transaction = tx_context.execute().await?; - bank_account.apply_delta(&executed_transaction.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_transaction)?; - mock_chain.prove_next_block()?; - - println!("Deposit transaction executed!"); - - // ========================================================================= - // VERIFY: Check balance was updated - // ========================================================================= - let depositor_key = Word::from([ - sender.id().prefix().as_felt(), - sender.id().suffix(), - faucet.id().prefix().as_felt(), - faucet.id().suffix(), - ]); - - let balance = bank_account.storage().get_map_item(&balances_slot, depositor_key)?; - - // Balance is stored as a single Felt in the last position of the Word - let balance_value = balance[3].as_int(); - - println!("Depositor balance: {}", balance_value); - assert_eq!( - balance_value, - deposit_amount, - "Balance should equal deposited amount" - ); - - println!("\nPart 3 deposit test passed!"); - - Ok(()) -} -``` - -:::note Test Dependencies -This test requires: -- `deposit-note` contract (Part 4) -- `init-tx-script` contract (Part 6) - -If you haven't created these yet, you can run this test after completing Parts 4 and 6, or create placeholder contracts. For now, let's verify the bank-account compiles correctly. -::: - -Build verification: - -```bash title=">_ Terminal" -cd contracts/bank-account -miden build -``` - -If you have the note scripts ready, run the full test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_deposit_updates_balance -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part3_deposit_test.rs - -running 1 test -Bank initialized successfully! -Deposit transaction executed! -Depositor balance: 1000 - -Part 3 deposit test passed! -test test_deposit_updates_balance ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -## Asset Flow Summary - -```text -DEPOSIT FLOW: -┌───────────┐ deposit_note ┌────────────┐ -│ Depositor │ ──────────────────▶ Bank Vault │ -│ Wallet │ (with asset) │ + Balance │ -└───────────┘ └────────────┘ - -WITHDRAW FLOW: -┌────────────┐ P2ID note ┌───────────┐ -│ Bank Vault │ ──────────────────▶ Depositor│ -│ - Balance │ (with asset) │ Wallet │ -└────────────┘ └───────────┘ -``` - -## Complete Code for This Part - -Here's the full `lib.rs` after Part 3: - -
-Click to expand full code - -```rust title="contracts/bank-account/src/lib.rs" -#![no_std] -#![feature(alloc_error_handler)] - -#[macro_use] -extern crate alloc; - -use miden::*; - -/// Maximum allowed deposit amount per transaction. -const MAX_DEPOSIT_AMOUNT: u64 = 1_000_000; - -/// Bank account component that tracks depositor balances. -#[component] -struct Bank { - #[storage(description = "initialized")] - initialized: Value, - - #[storage(description = "balances")] - balances: StorageMap, -} - -#[component] -impl Bank { - /// Initialize the bank account, enabling deposits. - pub fn initialize(&mut self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 0, - "Bank already initialized" - ); - - let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]); - self.initialized.write(initialized_word); - } - - /// Get the balance for a depositor. - pub fn get_balance(&self, depositor: AccountId) -> Felt { - let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]); - self.balances.get(&key) - } - - /// Check that the bank is initialized. - fn require_initialized(&self) { - let current: Word = self.initialized.read(); - assert!( - current[0].as_u64() == 1, - "Bank not initialized - deposits not enabled" - ); - } - - /// Deposit assets into the bank. - pub fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) { - self.require_initialized(); - - let deposit_amount = deposit_asset.inner[0]; - - assert!( - deposit_amount.as_u64() <= MAX_DEPOSIT_AMOUNT, - "Deposit amount exceeds maximum allowed" - ); - - let key = Word::from([ - depositor.prefix, - depositor.suffix, - deposit_asset.inner[3], - deposit_asset.inner[2], - ]); - - let current_balance: Felt = self.balances.get(&key); - let new_balance = current_balance + deposit_amount; - self.balances.set(key, new_balance); - - native_account::add_asset(deposit_asset); - } - - /// Withdraw assets from the bank. - pub fn withdraw( - &mut self, - depositor: AccountId, - withdraw_asset: Asset, - serial_num: Word, - tag: Felt, - note_type: Felt, - ) { - self.require_initialized(); - - let withdraw_amount = withdraw_asset.inner[0]; - - let key = Word::from([ - depositor.prefix, - depositor.suffix, - withdraw_asset.inner[3], - withdraw_asset.inner[2], - ]); - - // CRITICAL: Validate balance BEFORE subtraction - let current_balance: Felt = self.balances.get(&key); - assert!( - current_balance.as_u64() >= withdraw_amount.as_u64(), - "Withdrawal amount exceeds available balance" - ); - - let new_balance = current_balance - withdraw_amount; - self.balances.set(key, new_balance); - - self.create_p2id_note(serial_num, &withdraw_asset, depositor, tag, note_type); - } - - /// Create a P2ID note - placeholder for Part 7. - fn create_p2id_note( - &mut self, - _serial_num: Word, - _asset: &Asset, - _recipient_id: AccountId, - _tag: Felt, - _note_type: Felt, - ) { - todo!("P2ID note creation - see Part 7") - } -} -``` - -
- -## Key Takeaways - -1. **Asset layout**: `[amount, 0, faucet_suffix, faucet_prefix]` -2. **`native_account::add_asset()`** adds assets to the vault -3. **`native_account::remove_asset()`** removes assets from the vault (Part 7) -4. **Balance tracking** is application-level logic using `StorageMap` -5. **Composite keys** allow per-user, per-asset balance tracking -6. **CRITICAL: Always validate before subtraction** - Felt arithmetic wraps silently! - -:::tip View Complete Source -See the complete deposit and withdraw implementations in the [miden-bank repository](https://github.com/keinberger/miden-bank/blob/main/contracts/bank-account/src/lib.rs). -::: - -## Next Steps - -Now that you understand asset management, let's learn how to trigger these operations with [Part 4: Note Scripts](./note-scripts). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/04-note-scripts.md b/docs/builder/tutorials/rust-compiler/miden-bank/04-note-scripts.md deleted file mode 100644 index b2cd4d24..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/04-note-scripts.md +++ /dev/null @@ -1,534 +0,0 @@ ---- -sidebar_position: 4 -title: "Part 4: Note Scripts" -description: "Learn how to write note scripts that execute when notes are consumed, using active_note APIs to access sender, assets, and inputs." ---- - -# Part 4: Note Scripts - -In this section, you'll learn how to write note scripts - code that executes when a note is consumed by an account. We'll create the deposit note that lets users deposit tokens into the bank. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Created the `deposit-note` contract -- Understood the `#[note]` struct+impl pattern and `#[note_script]` method attribute -- Used `active_note` APIs to access sender and assets -- Built the note script and its dependencies -- **Verified it works** with a complete deposit flow test - -## Building on Part 3 - -In Part 3, we completed the bank's deposit method. Now we need a way to trigger it: - -```text -Part 3: Part 4: -┌──────────────────┐ ┌──────────────────┐ -│ Bank (complete) │ │ Bank (complete) │ -│ ─────────────────│ │ ─────────────────│ -│ + deposit() │ │ + deposit() │ -│ + withdraw() │ │ + withdraw() │ -└──────────────────┘ └──────────────────┘ - ▲ - │ calls - ┌────────────────────┐ - │ deposit-note │ ◄── NEW - │ (note script) │ - └────────────────────┘ -``` - -## Note Scripts vs Account Components - -| Feature | Account Component | Note Script | -|---------|------------------|-------------| -| Purpose | Persistent account logic | One-time execution when consumed | -| Storage | Has persistent storage | No storage (reads from note data) | -| Attribute | `#[component]` | `#[note]` struct + `#[note_script]` method | -| Entry point | Methods on struct | `fn run(self, _arg: Word)` | -| Invocation | Called by other contracts | Executes when note is consumed | - -Note scripts are like "messages" that carry code along with data and assets. - -## Step 1: Create the Deposit Note Project - -First, create the deposit-note contract. If you used `miden new`, you may have an `increment-note` folder - rename or replace it: - -```bash title=">_ Terminal" -# Remove or rename the example -rm -rf contracts/increment-note -# Or: mv contracts/increment-note contracts/increment-note-backup - -# Create the deposit-note directory -mkdir -p contracts/deposit-note/src -``` - -## Step 2: Configure Cargo.toml - -Create the `Cargo.toml` for the deposit note: - -```toml title="contracts/deposit-note/Cargo.toml" -[package] -name = "deposit-note" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -miden = { version = "0.10" } - -[package.metadata.component] -package = "miden:deposit-note" - -[package.metadata.miden] -project-kind = "note-script" - -# Dependencies on account components -[package.metadata.miden.dependencies] -"miden:bank-account" = { path = "../bank-account" } - -[package.metadata.component.target.dependencies] -"miden:bank-account" = { path = "../bank-account/target/generated-wit/" } -``` - -Key configuration: -- `project-kind = "note-script"` - Marks this as a note script -- Dependencies sections declare which accounts it can interact with - -## Step 3: Implement the Deposit Note - -Create the note script implementation: - -```rust title="contracts/deposit-note/src/lib.rs" -#![no_std] -#![feature(alloc_error_handler)] - -use miden::*; - -// Import the bank account's generated bindings -use crate::bindings::miden::bank_account::bank_account; - -/// Deposit Note Script -/// -/// When consumed by the Bank account, this note transfers all its assets -/// to the bank and credits the depositor (note sender) with the deposited amount. -#[note] -struct DepositNote; - -#[note] -impl DepositNote { - #[note_script] - fn run(self, _arg: Word) { - // The depositor is whoever created/sent this note - let depositor = active_note::get_sender(); - - // Get all assets attached to this note - let assets = active_note::get_assets(); - - // Deposit each asset into the bank - for asset in assets { - bank_account::deposit(depositor, asset); - } - } -} -``` - -### The #[note] and #[note_script] Attributes - -The `#[note]` attribute is applied to both a unit struct and its `impl` block to define a note script. Within the `impl` block, the `#[note_script]` attribute marks the entry point method. The function signature is always: - -```rust -fn run(self, _arg: Word) -``` - -The method takes `self` as its first parameter. The `_arg` parameter can pass additional data, but we don't use it in the deposit note. - -## Note Context APIs - -The `active_note` module provides APIs to access note data during execution: - -### get_sender() - Who Created the Note - -```rust -let depositor = active_note::get_sender(); -``` - -Returns the `AccountId` of the account that created/sent the note. In our bank: -- The sender is the depositor -- Their ID is used to credit their balance - -### get_assets() - Attached Assets - -```rust -let assets = active_note::get_assets(); -for asset in assets { - // Process each asset -} -``` - -Returns an iterator over all assets attached to the note. - -### get_inputs() - Note Parameters - -```rust -let inputs = active_note::get_inputs(); -let first_input = inputs[0]; -``` - -Returns a vector of `Felt` values passed when the note was created. We'll use inputs in the withdraw request note (Part 7). - -## Step 4: Update the Workspace - -Update the root `Cargo.toml` to include the new contract: - -```toml title="Cargo.toml" {5} -[workspace] -members = [ - "integration" -] -exclude = [ - "contracts/", -] -resolver = "2" - -[workspace.package] -edition = "2021" - -[workspace.dependencies] -``` - -## Step 5: Build the Note Script - -:::info Build Order Matters -Build account components **first** before building note scripts that depend on them. The note script needs the generated WIT files from the account. -::: - -```bash title=">_ Terminal" -# First, ensure bank-account is built (generates WIT files) -cd contracts/bank-account -miden build - -# Now build the deposit note -cd ../deposit-note -miden build -``` - -
-Expected output - -```text - Compiling deposit-note v0.1.0 - Finished `release` profile [optimized] target(s) -Creating Miden package /path/to/miden-bank/target/miden/release/deposit_note.masp -``` - -
- -## Execution Flow Diagram - -```text -1. User creates deposit note with 100 tokens attached - ┌───────────────────────────────────────┐ - │ Note: deposit-note │ - │ Sender: User's AccountId │ - │ Assets: [100 tokens] │ - └───────────────────────────────────────┘ - -2. Bank account consumes the note - ┌───────────────────────────────────────┐ - │ Bank receives assets into vault │ - │ Note script executes... │ - └───────────────────────────────────────┘ - -3. Note script runs - depositor = get_sender() → User's AccountId - assets = get_assets() → [100 tokens] - bank_account::deposit(depositor, 100 tokens) - -4. Bank's deposit() method executes - - Validates initialization and amount - - Updates balance: balances[User] += 100 - - Adds asset to vault -``` - -## Try It: Verify Deposits Work - -Now let's write a test to verify the complete deposit flow. This test: -1. Initializes the bank -2. Creates a deposit note with tokens -3. Has the bank consume the note -4. Verifies the balance was updated - -```rust title="integration/tests/part4_deposit_note_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, - create_testing_note_from_package, AccountCreationConfig, NoteCreationConfig, -}; -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; -use miden_client::note::NoteAssets; -use miden_client::transaction::{OutputNote, TransactionScript}; -use miden_client::asset::{Asset, FungibleAsset}; -use miden_client::{Felt, Word}; -use miden_testing::{Auth, MockChain}; -use std::{path::Path, sync::Arc}; - -#[tokio::test] -async fn test_deposit_note_credits_depositor() -> anyhow::Result<()> { - // ========================================================================= - // SETUP: Build contracts and create mock chain - // ========================================================================= - let mut builder = MockChain::builder(); - - // Create a faucet for test tokens - let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth, "TEST", 10_000_000, Some(10))?; - - // Create sender (depositor) wallet - let sender = builder.add_existing_wallet_with_assets(Auth::BasicAuth, [FungibleAsset::new(faucet.id(), 1000)?.into()])?; - - // Build all contracts - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - - let deposit_note_package = Arc::new(build_project_in_dir( - Path::new("../contracts/deposit-note"), - true, - )?); - - let init_tx_script_package = Arc::new(build_project_in_dir( - Path::new("../contracts/init-tx-script"), - true, - )?); - - // Create bank account - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot, Word::default()), - StorageSlot::with_map( - balances_slot.clone(), - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - - let mut bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - builder.add_account(bank_account.clone())?; - - // Create the deposit note and add it before building the chain - let deposit_amount: u64 = 1000; - let fungible_asset = FungibleAsset::new(faucet.id(), deposit_amount)?; - let note_assets = NoteAssets::new(vec![Asset::Fungible(fungible_asset)])?; - - let deposit_note = create_testing_note_from_package( - deposit_note_package.clone(), - sender.id(), // Sender is the depositor - NoteCreationConfig { - assets: note_assets, - ..Default::default() - }, - )?; - - builder.add_output_note(OutputNote::Full(deposit_note.clone())); - let mut mock_chain = builder.build()?; - - // ========================================================================= - // STEP 1: Initialize the bank - // ========================================================================= - let init_program = init_tx_script_package.unwrap_program(); - let init_tx_script = TransactionScript::new((*init_program).clone()); - - let init_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[], &[])? - .tx_script(init_tx_script) - .build()?; - - let executed_init = init_tx_context.execute().await?; - bank_account.apply_delta(&executed_init.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_init)?; - mock_chain.prove_next_block()?; - - println!("Step 1: Bank initialized"); - - // ========================================================================= - // STEP 2: Execute deposit - // ========================================================================= - let tx_context = mock_chain - .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])? - .build()?; - - let executed_transaction = tx_context.execute().await?; - bank_account.apply_delta(&executed_transaction.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_transaction)?; - mock_chain.prove_next_block()?; - - println!("Step 2: Deposit note consumed"); - - // ========================================================================= - // VERIFY: Balance was updated - // ========================================================================= - let depositor_key = Word::from([ - sender.id().prefix().as_felt(), - sender.id().suffix(), - faucet.id().prefix().as_felt(), - faucet.id().suffix(), - ]); - - let balance = bank_account.storage().get_map_item(&balances_slot, depositor_key)?; - let balance_value = balance[3].as_int(); - - println!("Step 3: Verified balance = {}", balance_value); - - assert_eq!( - balance_value, - deposit_amount, - "Balance should equal deposited amount" - ); - - println!("\nPart 4 deposit note test passed!"); - - Ok(()) -} -``` - -:::note Dependencies -This test requires the `init-tx-script` contract which we'll create in Part 6. You can either: -1. Skip ahead to create a minimal init-tx-script (see Part 6) -2. Run this test after completing Part 6 - -For now, verify that your deposit-note builds successfully. -::: - -Run the test from the project root (after creating init-tx-script in Part 6): - -```bash title=">_ Terminal" -cargo test --package integration test_deposit_note_credits_depositor -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part4_deposit_note_test.rs - -running 1 test -Step 1: Bank initialized -Step 2: Deposit note consumed -Step 3: Verified balance = 1000 - -Part 4 deposit note test passed! -test test_deposit_note_credits_depositor ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -## Preview: Withdraw Request Note - -For withdrawals, we'll use note inputs to pass parameters. Here's a preview of the withdraw request note (implemented in Part 7): - -```rust title="contracts/withdraw-request-note/src/lib.rs (preview)" -/// Withdraw Request Note Script -/// -/// # Note Inputs (10 Felts) -/// [0-3]: withdraw asset (amount, 0, faucet_suffix, faucet_prefix) -/// [4-7]: serial_num (random/unique per note) -/// [8]: tag (P2ID note tag for routing) -/// [9]: note_type (1 = Public, 2 = Private) -#[note] -struct WithdrawRequestNote; - -#[note] -impl WithdrawRequestNote { - #[note_script] - fn run(self, _arg: Word) { - let depositor = active_note::get_sender(); - let inputs = active_note::get_inputs(); - - // Parse parameters from inputs - let withdraw_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] - ]); - - let tag = inputs[8]; - let note_type = inputs[9]; - - bank_account::withdraw(depositor, withdraw_asset, serial_num, tag, note_type); - } -} -``` - -:::warning Stack Limits -Note inputs are limited. Keep your input layout compact. See [Common Pitfalls](../pitfalls) for stack-related constraints. -::: - -## Complete Code for This Part - -
-Click to expand deposit-note/src/lib.rs - -```rust title="contracts/deposit-note/src/lib.rs" -#![no_std] -#![feature(alloc_error_handler)] - -use miden::*; - -use crate::bindings::miden::bank_account::bank_account; - -/// Deposit Note Script -#[note] -struct DepositNote; - -#[note] -impl DepositNote { - #[note_script] - fn run(self, _arg: Word) { - let depositor = active_note::get_sender(); - let assets = active_note::get_assets(); - - for asset in assets { - bank_account::deposit(depositor, asset); - } - } -} -``` - -
- -## Key Takeaways - -1. **`#[note]`** marks the struct and impl block, with **`#[note_script]`** on the entry point method `fn run(self, _arg: Word)` -2. **`active_note::get_sender()`** returns who created the note -3. **`active_note::get_assets()`** returns assets attached to the note -4. **`active_note::get_inputs()`** returns parameterized data -5. **Note scripts execute once** when consumed - no persistent state -6. **Build order matters** - account components first, then note scripts - -:::tip View Complete Source -See the complete note script implementations: -- [Deposit Note](https://github.com/keinberger/miden-bank/blob/main/contracts/deposit-note/src/lib.rs) -- [Withdraw Request Note](https://github.com/keinberger/miden-bank/blob/main/contracts/withdraw-request-note/src/lib.rs) -::: - -## Next Steps - -Now that you understand note scripts, let's learn how they call account methods in [Part 5: Cross-Component Calls](./cross-component-calls). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/05-cross-component-calls.md b/docs/builder/tutorials/rust-compiler/miden-bank/05-cross-component-calls.md deleted file mode 100644 index bb7a1d23..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/05-cross-component-calls.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -sidebar_position: 5 -title: "Part 5: Cross-Component Calls" -description: "Learn how note scripts and transaction scripts call account component methods using generated bindings and proper dependency configuration." ---- - -# Part 5: Cross-Component Calls - -In this section, you'll learn how note scripts call methods on account components. We'll explore the generated bindings system and the dependency configuration that makes the deposit note work. - -## What You'll Learn in This Part - -By the end of this section, you will have: - -- Understood how bindings are generated and imported -- Learned the dependency configuration in `Cargo.toml` -- Explored the WIT interface files -- **Verified cross-component calls work** via the deposit flow - -## Building on Part 4 - -In Part 4, you wrote `bank_account::deposit(depositor, asset)` in the deposit note. But how does that call actually work? This part explains the binding system: - -```text -┌────────────────────────────────────────────────────────────┐ -│ How Bindings Work │ -├────────────────────────────────────────────────────────────┤ -│ │ -│ bank-account/ │ -│ └── src/lib.rs miden build │ -│ pub fn deposit() ─────────────▶ generated-wit/ │ -│ pub fn withdraw() miden_bank-account.wit -│ │ -│ ┌───────────────────────────┐ │ -│ ▼ │ │ -│ deposit-note/ │ │ -│ └── src/lib.rs │ │ -│ use crate::bindings::miden::bank_account::bank_account; -│ bank_account::deposit(...) ◄───── calls via binding │ -│ │ -└────────────────────────────────────────────────────────────┘ -``` - -## The Bindings System - -When you build an account component with `miden build`, it generates: - -1. **MASM code** - The compiled contract logic -2. **WIT files** - WebAssembly Interface Type definitions - -Other contracts (note scripts, transaction scripts) import these WIT files to call the account's methods. - -```text -Build Flow: -┌──────────────────┐ miden build ┌─────────────────────────────────┐ -│ bank-account/ │ ─────────────────▶│ target/generated-wit/ │ -│ src/lib.rs │ │ miden_bank-account.wit │ -│ │ │ miden_bank-account_world.wit │ -└──────────────────┘ └─────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────┐ - │ deposit-note/ │ - │ imports generated bindings │ - └─────────────────────────────────┘ -``` - -## Importing Bindings - -In your note script, import the generated bindings: - -```rust title="contracts/deposit-note/src/lib.rs" -// Import the bank account's generated bindings -use crate::bindings::miden::bank_account::bank_account; -``` - -The import path follows this pattern: -``` -crate::bindings::{package-prefix}::{component-name}::{interface-name} -``` - -For our bank: -- `miden` - The package prefix from `[package.metadata.component]` -- `bank_account` - The component name (derived from package name with underscores) -- `bank_account` - The interface name (same as component) - -## Calling Account Methods - -Once imported, call the account methods directly: - -```rust title="contracts/deposit-note/src/lib.rs" -#[note] -struct DepositNote; - -#[note] -impl DepositNote { - #[note_script] - fn run(self, _arg: Word) { - let depositor = active_note::get_sender(); - let assets = active_note::get_assets(); - - for asset in assets { - // Call the bank account's deposit method - bank_account::deposit(depositor, asset); - } - } -} -``` - -The binding automatically handles: -- Marshalling arguments across the component boundary -- Invoking the correct MASM procedures -- Returning results back to the caller - -## Configuring Dependencies - -Your `Cargo.toml` needs **two** dependency sections: - -```toml title="contracts/deposit-note/Cargo.toml" -[package.metadata.miden.dependencies] -"miden:bank-account" = { path = "../bank-account" } - -[package.metadata.component.target.dependencies] -"miden:bank-account" = { path = "../bank-account/target/generated-wit/" } -``` - -### miden.dependencies - -```toml -[package.metadata.miden.dependencies] -"miden:bank-account" = { path = "../bank-account" } -``` - -This tells `cargo-miden` where to find the source package. Used during the build process to: -- Verify interface compatibility -- Link the compiled MASM code - -### component.target.dependencies - -```toml -[package.metadata.component.target.dependencies] -"miden:bank-account" = { path = "../bank-account/target/generated-wit/" } -``` - -This tells the Rust compiler where to find the WIT interface files. The path points to the `generated-wit/` directory created when you built the account component. - -:::warning Both Sections Required -If either section is missing, your build will fail with linking or interface errors. -::: - -## Build Order - -Components must be built in dependency order: - -```bash title=">_ Terminal" -# 1. Build the account component first -cd contracts/bank-account -miden build - -# 2. Then build note scripts that depend on it -cd ../deposit-note -miden build -``` - -If you build out of order, you'll see errors about missing WIT files. - -## What Methods Are Available? - -Only **public methods** (`pub fn`) on the `#[component] impl` block are available through bindings: - -```rust title="contracts/bank-account/src/lib.rs" -#[component] -impl Bank { - // PUBLIC: Available through bindings - pub fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) { ... } - pub fn withdraw(&mut self, /* ... */) { ... } - pub fn get_balance(&self, depositor: AccountId) -> Felt { ... } - pub fn initialize(&mut self) { ... } - - // PRIVATE: NOT available through bindings - fn require_initialized(&self) { ... } - fn create_p2id_note(&mut self, /* ... */) { ... } -} -``` - -## Understanding the Generated WIT - -The WIT files describe the interface. Here's a simplified example: - -```wit title="target/generated-wit/miden_bank-account.wit" -interface bank-account { - use miden:types/types.{account-id, asset, felt, word}; - - initialize: func(); - deposit: func(depositor: account-id, deposit-asset: asset); - withdraw: func(depositor: account-id, withdraw-asset: asset, ...); - get-balance: func(depositor: account-id) -> felt; -} -``` - -This WIT is used to generate the Rust bindings that appear in `crate::bindings`. - -## Transaction Script Bindings (Preview) - -Transaction scripts use a slightly different import pattern: - -```rust title="contracts/init-tx-script/src/lib.rs" -use crate::bindings::Account; - -#[tx_script] -fn run(_arg: Word, account: &mut Account) { - // The account parameter IS the bound component - account.initialize(); -} -``` - -The `Account` binding in transaction scripts wraps the entire component, giving direct method access through the `account` parameter. We'll implement this in Part 6. - -## Try It: Verify Bindings Work - -If you completed Part 4 and built both contracts, the bindings are already working! Let's verify: - -```bash title=">_ Terminal" -# Check that the WIT files were generated -ls contracts/bank-account/target/generated-wit/ -``` - -
-Expected output - -```text -miden_bank-account.wit -miden_bank-account_world.wit -``` - -
- -These files enable the deposit note to call `bank_account::deposit()`. - -## Common Issues - -### "Cannot find module" Error - -``` -error: cannot find module `bindings` -``` - -**Cause**: The account component wasn't built, or the WIT path is wrong. - -**Solution**: -1. Build the account: `cd contracts/bank-account && miden build` -2. Verify the WIT path in `Cargo.toml` points to `target/generated-wit/` - -### "Method not found" Error - -``` -error: no method named `deposit` found -``` - -**Cause**: The method isn't marked `pub` in the account component. - -**Solution**: Ensure the method has `pub fn` visibility. - -### "Dependency not found" Error - -``` -error: dependency 'miden:bank-account' not found -``` - -**Cause**: One of the dependency sections is missing or has the wrong path. - -**Solution**: Ensure both `[package.metadata.miden.dependencies]` and `[package.metadata.component.target.dependencies]` are present with correct paths. - -## Key Takeaways - -1. **Build accounts first** - They generate WIT files that note scripts need -2. **Two dependency sections** - Both `miden.dependencies` and `component.target.dependencies` are required -3. **Import path pattern** - `crate::bindings::{package}::{component}::{interface}` -4. **Only public methods** - Private methods aren't exposed in bindings -5. **Transaction scripts differ** - They receive the account as a parameter (Part 6) - -:::tip View Complete Source -See the complete Cargo.toml configurations: -- [Deposit Note Cargo.toml](https://github.com/keinberger/miden-bank/blob/main/contracts/deposit-note/Cargo.toml) -- [Withdraw Request Note Cargo.toml](https://github.com/keinberger/miden-bank/blob/main/contracts/withdraw-request-note/Cargo.toml) -::: - -## Next Steps - -Now that you understand cross-component calls, let's create the transaction script that initializes the bank in [Part 6: Transaction Scripts](./transaction-scripts). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/06-transaction-scripts.md b/docs/builder/tutorials/rust-compiler/miden-bank/06-transaction-scripts.md deleted file mode 100644 index 6254097c..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/06-transaction-scripts.md +++ /dev/null @@ -1,488 +0,0 @@ ---- -sidebar_position: 6 -title: "Part 6: Transaction Scripts" -description: "Learn how to write transaction scripts for account initialization and owner-controlled operations using the #[tx_script] attribute." ---- - -# Part 6: Transaction Scripts - -In this section, you'll learn how to write transaction scripts - code that the account owner explicitly executes. We'll implement an initialization script that enables the bank to accept deposits. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Created the `init-tx-script` transaction script project -- Understood the `#[tx_script]` attribute and function signature -- Learned the difference between transaction scripts and note scripts -- **Verified initialization works** via a MockChain test - -## Building on Part 5 - -In Parts 4-5, you created note scripts that execute when notes are consumed. Now you'll create a transaction script - code the account owner explicitly runs: - -```text -┌────────────────────────────────────────────────────────────────┐ -│ Script Types Comparison │ -├────────────────────────────────────────────────────────────────┤ -│ │ -│ Note Scripts (Parts 4-5) Transaction Scripts (Part 6)│ -│ ───────────────────────── ────────────────────────────│ -│ • Triggered by note consumption • Explicitly called by owner│ -│ • Import bindings via modules • Receive account parameter │ -│ • Process incoming assets • Setup, admin operations │ -│ │ -│ deposit-note/ init-tx-script/ │ -│ └── calls bank_account::deposit() └── calls account.initialize() -│ │ -└────────────────────────────────────────────────────────────────┘ -``` - -## Transaction Scripts vs Note Scripts - -| Aspect | Transaction Script | Note Script | -|--------|-------------------|-------------| -| Initiation | Explicitly called by account owner | Triggered when note is consumed | -| Access | Direct account method access | Must call through bindings | -| Use case | Setup, owner operations | Receiving messages/assets | -| Parameter | `account: &mut Account` | Note context via `active_note::` | - -**Use transaction scripts for:** -- One-time initialization -- Admin/owner operations -- Operations that don't involve receiving notes - -**Use note scripts for:** -- Receiving assets from other accounts -- Processing requests from other accounts -- Multi-party interactions - -## Step 1: Create the Transaction Script Project - -Create a new directory for the transaction script: - -```bash title=">_ Terminal" -mkdir -p contracts/init-tx-script/src -``` - -## Step 2: Configure Cargo.toml - -Create the Cargo.toml with transaction script configuration: - -```toml title="contracts/init-tx-script/Cargo.toml" -[package] -name = "init-tx-script" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -miden = { version = "0.10" } - -[package.metadata.component] -package = "miden:init-tx-script" - -[package.metadata.miden] -project-kind = "transaction-script" - -[package.metadata.miden.dependencies] -"miden:bank-account" = { path = "../bank-account" } - -[package.metadata.component.target.dependencies] -"miden:bank-account" = { path = "../bank-account/target/generated-wit/" } -``` - -Key configuration: -- `project-kind = "transaction-script"` - Marks this as a transaction script (not "account" or "note") -- Dependencies reference the account component (same pattern as note scripts) - -## Step 3: Add to Workspace - -Update your root `Cargo.toml` to include the new project: - -```toml title="Cargo.toml" -[workspace] -members = [ - "integration" -] -exclude = [ - "contracts/", -] -resolver = "2" - -[workspace.package] -edition = "2021" - -[workspace.dependencies] -``` - -## Step 4: Implement the Transaction Script - -Create the initialization script: - -```rust title="contracts/init-tx-script/src/lib.rs" -// Do not link against libstd (i.e. anything defined in `std::`) -#![no_std] -#![feature(alloc_error_handler)] - -use miden::*; - -// Import the Account binding which wraps the bank-account component methods -use crate::bindings::Account; - -/// Initialize Transaction Script -/// -/// This transaction script initializes the bank account, enabling deposits. -/// It must be executed by the bank account owner before any deposits can be made. -/// -/// # Flow -/// 1. Transaction is created with this script attached -/// 2. Script executes in the context of the bank account -/// 3. Calls `account.initialize()` to enable deposits -/// 4. Bank account is now "deployed" and visible on chain -#[tx_script] -fn run(_arg: Word, account: &mut Account) { - account.initialize(); -} -``` - -## The #[tx_script] Attribute - -The `#[tx_script]` attribute marks the entry point for a transaction script: - -```rust -#[tx_script] -fn run(_arg: Word, account: &mut Account) { - account.initialize(); -} -``` - -### Function Signature - -| Parameter | Type | Description | -|-----------|------|-------------| -| `_arg` | `Word` | Optional argument passed when executing | -| `account` | `&mut Account` | Mutable reference to the account component | - -The `Account` type is generated from your component's bindings and provides access to all public methods. - -## The Account Binding - -Unlike note scripts that import bindings like `bank_account::deposit()`, transaction scripts receive the account as a parameter: - -```rust -// Note script style (indirect): -use crate::bindings::miden::bank_account::bank_account; -bank_account::deposit(depositor, asset); - -// Transaction script style (direct): -use crate::bindings::Account; -fn run(_arg: Word, account: &mut Account) { - account.initialize(); // Direct method call -} -``` - -The `Account` wrapper provides: -- Direct method access without module prefixes -- Proper mutable/immutable borrowing -- Automatic context binding - -## Step 5: Build the Transaction Script - -Build in dependency order: - -```bash title=">_ Terminal" -# First, ensure the account component is built (generates WIT files) -cd contracts/bank-account -miden build - -# Then build the transaction script -cd ../init-tx-script -miden build -``` - -
-Expected output - -```text - Compiling init-tx-script v0.1.0 - Finished `release` profile [optimized] target(s) -Creating Miden package /path/to/miden-bank/target/miden/release/init_tx_script.masp -``` - -
- -## Account Deployment Pattern - -In Miden, accounts are only visible on-chain after their first state change. Transaction scripts are commonly used for this "deployment": - -```text -Execution Flow: - -1. Account owner creates transaction with init-tx-script - ┌───────────────────────────────────────┐ - │ Transaction │ - │ Account: Bank's AccountId │ - │ Script: init-tx-script │ - └───────────────────────────────────────┘ - -2. Transaction executes - ┌───────────────────────────────────────┐ - │ run(_arg, account) │ - │ └─ account.initialize() │ - │ └─ Sets initialized flag to 1 │ - └───────────────────────────────────────┘ - -3. Account state updated - ┌───────────────────────────────────────┐ - │ Bank Account │ - │ Storage[0] = [1, 0, 0, 0] ← Initialized - │ Now visible on-chain │ - └───────────────────────────────────────┘ -``` - -Before initialization: -- Account exists locally but isn't visible on the network -- Cannot receive notes or interact with other accounts - -After initialization: -- Account is "deployed" and visible -- Can receive deposits and interact normally - -## Using Script Arguments - -The `_arg` parameter can pass data to the script: - -```rust title="Example: Parameterized script" -#[tx_script] -fn run(arg: Word, account: &mut Account) { - // Use arg as configuration - let config_value = arg[0]; - account.configure(config_value); -} -``` - -When creating the transaction, provide the argument: - -```rust title="Integration code (not contract code)" -let tx_script_args = Word::from([felt!(42), felt!(0), felt!(0), felt!(0)]); -let tx_context = mock_chain - .build_tx_context(bank_account.id(), &[], &[])? - .tx_script(init_tx_script) - .tx_script_args(tx_script_args) // Pass the argument - .build()?; -``` - -## Try It: Verify Initialization Works - -Let's test that the initialization transaction script enables deposits. - -Create a test file: - -```rust title="integration/tests/part6_tx_script_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, AccountCreationConfig, -}; -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; -use miden_client::Word; -use miden_client::transaction::TransactionScript; -use miden_testing::MockChain; -use std::{path::Path, sync::Arc}; - -/// Test that the init-tx-script properly initializes the bank account -#[tokio::test] -async fn test_init_tx_script_enables_deposits() -> anyhow::Result<()> { - // Build all required packages - let mut builder = MockChain::builder(); - - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - - let init_tx_script_package = Arc::new(build_project_in_dir( - Path::new("../contracts/init-tx-script"), - true, - )?); - - // Create uninitialized bank account with named storage slots - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot.clone(), Word::default()), - StorageSlot::with_map( - balances_slot, - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - - let mut bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - // Verify bank is NOT initialized - let initial_storage = bank_account.storage().get_item(&initialized_slot)?; - assert_eq!( - initial_storage[0].as_int(), - 0, - "Bank should start uninitialized" - ); - - println!("Step 1: Bank starts uninitialized (storage[0] = 0)"); - - // Add bank to mock chain - builder.add_account(bank_account.clone())?; - let mut mock_chain = builder.build()?; - - // Create the TransactionScript from our init-tx-script - let init_program = init_tx_script_package.unwrap_program(); - let init_tx_script = TransactionScript::new((*init_program).clone()); - - // Build and execute the initialization transaction - let init_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[], &[])? - .tx_script(init_tx_script) - .build()?; - - let executed_init = init_tx_context.execute().await?; - bank_account.apply_delta(&executed_init.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_init)?; - mock_chain.prove_next_block()?; - - // Verify bank IS now initialized - let final_storage = bank_account.storage().get_item(&initialized_slot)?; - assert_eq!( - final_storage[0].as_int(), - 1, - "Bank should be initialized after tx script" - ); - - println!("Step 2: Bank initialized via transaction script (storage[0] = 1)"); - println!("\nPart 6 transaction script test passed!"); - - Ok(()) -} -``` - -Run the test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_init_tx_script_enables_deposits -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part6_tx_script_test.rs - -running 1 test -✓ Bank successfully initialized via transaction script - Storage[0] changed from [0,0,0,0] to [1,0,0,0] - Bank is now ready to accept deposits! -test test_init_tx_script_enables_deposits ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -:::tip Troubleshooting -**"Cannot find module bindings"**: The bank-account wasn't built. Run `miden build` in `contracts/bank-account` first. - -**"Dependency not found"**: Check that both dependency sections are in Cargo.toml with correct paths. -::: - -## What We've Built So Far - -| Component | Status | Description | -|-----------|--------|-------------| -| `bank-account` | ✅ Complete | Full deposit logic with storage and constraints | -| `deposit-note` | ✅ Complete | Note script that calls deposit method | -| `init-tx-script` | ✅ Complete | Transaction script for initialization | -| `withdraw-request-note` | Not started | Coming in Part 7 | - -## Complete Code for This Part - -
-Click to see the complete init-tx-script code - -```rust title="contracts/init-tx-script/src/lib.rs" -// Do not link against libstd (i.e. anything defined in `std::`) -#![no_std] -#![feature(alloc_error_handler)] - -use miden::*; - -// Import the Account binding which wraps the bank-account component methods -use crate::bindings::Account; - -/// Initialize Transaction Script -/// -/// This transaction script initializes the bank account, enabling deposits. -/// It must be executed by the bank account owner before any deposits can be made. -/// -/// # Flow -/// 1. Transaction is created with this script attached -/// 2. Script executes in the context of the bank account -/// 3. Calls `account.initialize()` to enable deposits -/// 4. Bank account is now "deployed" and visible on chain -#[tx_script] -fn run(_arg: Word, account: &mut Account) { - account.initialize(); -} -``` - -```toml title="contracts/init-tx-script/Cargo.toml" -[package] -name = "init-tx-script" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -miden = { version = "0.10" } - -[package.metadata.component] -package = "miden:init-tx-script" - -[package.metadata.miden] -project-kind = "transaction-script" - -[package.metadata.miden.dependencies] -"miden:bank-account" = { path = "../bank-account" } - -[package.metadata.component.target.dependencies] -"miden:bank-account" = { path = "../bank-account/target/generated-wit/" } -``` - -
- -## Key Takeaways - -1. **`#[tx_script]`** marks the entry point with signature `fn run(_arg: Word, account: &mut Account)` -2. **Direct account access** - Methods called on the `account` parameter, not via module imports -3. **Owner-initiated** - Only the account owner can execute transaction scripts -4. **Deployment pattern** - First state change makes account visible on-chain -5. **Dependencies** - Same Cargo.toml configuration as note scripts - -:::tip View Complete Source -See the complete transaction script implementation in the [miden-bank repository](https://github.com/keinberger/miden-bank/blob/main/contracts/init-tx-script/src/lib.rs). -::: - -## Next Steps - -Now that you understand transaction scripts, let's learn the advanced topic of creating output notes in [Part 7: Creating Output Notes](./output-notes). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/07-output-notes.md b/docs/builder/tutorials/rust-compiler/miden-bank/07-output-notes.md deleted file mode 100644 index f4c38ed4..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/07-output-notes.md +++ /dev/null @@ -1,724 +0,0 @@ ---- -sidebar_position: 7 -title: "Part 7: Creating Output Notes" -description: "Learn how to create output notes programmatically within account methods, including the P2ID (Pay-to-ID) note pattern for sending assets." ---- - -# Part 7: Creating Output Notes - -In this section, you'll learn how to create output notes from within account methods. We'll implement the full withdrawal logic that creates P2ID (Pay-to-ID) notes to send assets back to depositors. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Created the `withdraw-request-note` note script project -- Implemented the `withdraw()` method with balance validation -- Implemented `create_p2id_note()` for sending assets -- **Verified withdrawals work** via a MockChain test - -## Building on Part 6 - -In Part 6, you created a transaction script for initialization. Now you'll complete the bank by implementing withdrawals that create output notes: - -```text -┌────────────────────────────────────────────────────────────────┐ -│ Complete Bank Flow │ -├────────────────────────────────────────────────────────────────┤ -│ │ -│ Part 6: Initialize │ -│ ┌─────────────────┐ init-tx-script ┌───────────────┐ │ -│ │ Bank (uninit) │ ──────────────────────▶│ Bank (ready) │ │ -│ └─────────────────┘ └───────────────┘ │ -│ │ -│ Part 4: Deposit │ -│ ┌─────────────────┐ deposit-note ┌───────────────┐ │ -│ │ User sends │ ──────────────────────▶│ Balance += X │ │ -│ │ deposit note │ │ Vault += X │ │ -│ └─────────────────┘ └───────────────┘ │ -│ │ -│ Part 7: Withdraw (NEW) │ -│ ┌─────────────────┐ withdraw-request ┌───────────────┐ │ -│ │ User sends │ ──────────────────────▶│ Balance -= X │ │ -│ │ withdraw note │ │ Creates P2ID │ │ -│ └─────────────────┘ │ output note │ │ -│ └───────────────┘ │ -│ │ -└────────────────────────────────────────────────────────────────┘ -``` - -## Output Notes Overview - -When an account needs to send assets to another account, it creates an **output note**. The note travels through the network until the recipient consumes it. - -```text -WITHDRAW FLOW: -┌────────────────┐ ┌────────────────┐ ┌────────────────┐ -│ Bank Account │ creates │ P2ID Note │ consumed │ Depositor │ -│ │ ────────▶│ (with assets) │ ────────▶│ Wallet │ -│ remove_asset() │ │ │ │ receives asset │ -└────────────────┘ └────────────────┘ └────────────────┘ -``` - -## The P2ID Note Pattern - -P2ID (Pay-to-ID) is a standard note pattern in Miden that sends assets to a specific account: - -- **Target account**: Only one account can consume the note -- **Asset transfer**: Assets are transferred on consumption -- **Standard script**: Uses a well-known script from miden-standards - -## Step 1: Add Withdraw Method to Bank Account - -First, let's add the `withdraw()` method to your bank account. Update `contracts/bank-account/src/lib.rs`: - -```rust title="contracts/bank-account/src/lib.rs" -#[component] -impl Bank { - // ... existing methods (initialize, deposit, get_balance) ... - - /// Withdraw assets back to the depositor. - /// - /// Creates a P2ID note that sends the requested asset to the depositor's account. - /// - /// # Arguments - /// * `depositor` - The AccountId of the user withdrawing - /// * `withdraw_asset` - The fungible asset to withdraw - /// * `serial_num` - Unique serial number for the P2ID output note - /// * `tag` - The note tag for the P2ID output note (allows caller to specify routing) - /// * `note_type` - Note type: 1 = Public (stored on-chain), 2 = Private (off-chain) - /// - /// # Panics - /// Panics if the withdrawal amount exceeds the depositor's current balance. - /// Panics if the bank has not been initialized. - pub fn withdraw( - &mut self, - depositor: AccountId, - withdraw_asset: Asset, - serial_num: Word, - tag: Felt, - note_type: Felt, - ) { - // Ensure the bank is initialized before processing withdrawals - self.require_initialized(); - - // Extract the fungible amount from the asset - let withdraw_amount = withdraw_asset.inner[0]; - - // Create key from depositor's AccountId and asset faucet ID - let key = Word::from([ - depositor.prefix, - depositor.suffix, - withdraw_asset.inner[3], // asset prefix (faucet) - withdraw_asset.inner[2], // asset suffix (faucet) - ]); - - // Get current balance and validate sufficient funds exist. - // This check is critical: Felt arithmetic is modular, so subtracting - // more than the balance would silently wrap to a large positive number. - let current_balance: Felt = self.balances.get(&key); - assert!( - current_balance.as_u64() >= withdraw_amount.as_u64(), - "Withdrawal amount exceeds available balance" - ); - - // Update balance: current - withdraw_amount - let new_balance = current_balance - withdraw_amount; - self.balances.set(key, new_balance); - - // Create a P2ID note to send the requested asset back to the depositor - self.create_p2id_note(serial_num, &withdraw_asset, depositor, tag, note_type); - } -} -``` - -:::danger Critical Security: Balance Validation -Always validate `current_balance >= withdraw_amount` BEFORE subtraction. Miden uses modular field arithmetic - subtracting a larger value silently wraps to a massive positive number! -::: - -## Step 2: Add the P2ID Note Root - -The P2ID note uses a standard script from miden-standards. Add this helper function: - -```rust title="contracts/bank-account/src/lib.rs" -#[component] -impl Bank { - // ... other methods ... - - /// Returns the P2ID note script root digest. - /// - /// This is a constant value derived from the standard P2ID note script in miden-standards. - /// The digest is the MAST root of the compiled P2ID note script. - fn p2id_note_root() -> Digest { - Digest::from_word(Word::new([ - Felt::from_u64_unchecked(13362761878458161062), - Felt::from_u64_unchecked(15090726097241769395), - Felt::from_u64_unchecked(444910447169617901), - Felt::from_u64_unchecked(3558201871398422326), - ])) - } -} -``` - -:::warning Version-Specific -This digest is specific to miden-standards version. If the P2ID script changes in a future version, this digest must be updated. -::: - -## Step 3: Implement create_p2id_note - -Add the private method that creates the output note: - -```rust title="contracts/bank-account/src/lib.rs" -#[component] -impl Bank { - // ... other methods ... - - /// Create a P2ID (Pay-to-ID) note to send assets to a recipient. - /// - /// # Arguments - /// * `serial_num` - Unique serial number for the note - /// * `asset` - The asset to include in the note - /// * `recipient_id` - The AccountId that can consume this note - /// * `tag` - The note tag (passed by caller to allow proper P2ID routing) - /// * `note_type` - Note type as Felt: 1 = Public, 2 = Private - fn create_p2id_note( - &mut self, - serial_num: Word, - asset: &Asset, - recipient_id: AccountId, - tag: Felt, - note_type: Felt, - ) { - // Convert the passed tag Felt to a Tag - // The caller is responsible for computing the proper P2ID tag - // (typically with_account_target for the recipient) - let tag = Tag::from(tag); - - // Convert note_type Felt to NoteType - // 1 = Public (stored on-chain), 2 = Private (off-chain) - let note_type = NoteType::from(note_type); - - // Get the P2ID note script root digest - let script_root = Self::p2id_note_root(); - - // Compute the recipient hash from: - // - serial_num: unique identifier for this note instance - // - script_root: the P2ID note script's MAST root - // - inputs: the target account ID - // - // The P2ID script expects inputs as [suffix, prefix] - let recipient = Recipient::compute( - serial_num, - script_root, - vec![ - recipient_id.suffix, - recipient_id.prefix, - ], - ); - - // Create the output note - let note_idx = output_note::create(tag, note_type, recipient); - - // Remove the asset from the bank's vault - native_account::remove_asset(asset.clone()); - - // Add the asset to the output note - output_note::add_asset(asset.clone(), note_idx); - } -} -``` - -### Understanding Recipient::compute() - -| Parameter | Description | -|-----------|-------------| -| `serial_num` | Unique 4-Felt value preventing note reuse | -| `script_root` | The P2ID script's MAST root digest | -| `inputs` | Script inputs (account ID for P2ID) | - -:::warning Array Ordering -Note the order: `suffix` comes before `prefix`. This is the opposite of how `AccountId` fields are typically accessed. See [Common Pitfalls](../pitfalls#array-ordering-rustmasm-reversal) for details. -::: - -### Understanding output_note::create() - -| Parameter | Type | Description | -|-----------|------|-------------| -| `tag` | `Tag` | Routing information for the note | -| `note_type` | `NoteType` | Public (1) or Private (2) | -| `recipient` | `Recipient` | Who can consume the note | - -## Step 4: Create the Withdraw Request Note Project - -Create the directory structure: - -```bash title=">_ Terminal" -mkdir -p contracts/withdraw-request-note/src -``` - -### Configure Cargo.toml - -```toml title="contracts/withdraw-request-note/Cargo.toml" -[package] -name = "withdraw-request-note" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -miden = { version = "0.10" } - -[package.metadata.component] -package = "miden:withdraw-request-note" - -[package.metadata.miden] -project-kind = "note-script" - -[package.metadata.miden.dependencies] -"miden:bank-account" = { path = "../bank-account" } - -[package.metadata.component.target.dependencies] -"miden:bank-account" = { path = "../bank-account/target/generated-wit/" } -``` - -### Update Workspace - -Add to your root `Cargo.toml`: - -```toml title="Cargo.toml" -[workspace] -members = [ - "integration" -] -exclude = [ - "contracts/", -] -resolver = "2" - -[workspace.package] -edition = "2021" - -[workspace.dependencies] -``` - -## Step 5: Implement the Withdraw Request Note Script - -```rust title="contracts/withdraw-request-note/src/lib.rs" -// Do not link against libstd (i.e. anything defined in `std::`) -#![no_std] -#![feature(alloc_error_handler)] - -use miden::*; - -// Import the bank account's generated bindings -use crate::bindings::miden::bank_account::bank_account; - -/// Withdraw Request Note Script -/// -/// When consumed by the Bank account, this note requests a withdrawal and -/// the bank creates a P2ID note to send assets back to the depositor. -/// -/// # Flow -/// 1. Note is created by a depositor specifying the withdrawal details -/// 2. Bank account consumes this note -/// 3. Note script reads the sender (depositor) and inputs -/// 4. Calls `bank_account::withdraw(depositor, asset, serial_num, tag, note_type)` -/// 5. Bank updates the depositor's balance -/// 6. Bank creates a P2ID note with the specified parameters to send assets back -/// -/// # Note Inputs (10 Felts) -/// [0-3]: withdraw asset (amount, 0, faucet_suffix, faucet_prefix) -/// [4-7]: serial_num (random/unique per note) -/// [8]: tag (P2ID note tag for routing) -/// [9]: note_type (1 = Public, 2 = Private) -#[note] -struct WithdrawRequestNote; - -#[note] -impl WithdrawRequestNote { - #[note_script] - fn run(self, _arg: Word) { - // The depositor is whoever created/sent this note - let depositor = active_note::get_sender(); - - // Get the inputs - let inputs = active_note::get_inputs(); - - // Asset: [amount, 0, faucet_suffix, faucet_prefix] - let withdraw_asset = Asset::new(Word::from([inputs[0], inputs[1], inputs[2], inputs[3]])); - - // Serial number: full 4 Felts (random/unique per note) - let serial_num = Word::from([inputs[4], inputs[5], inputs[6], inputs[7]]); - - // Tag: single Felt for P2ID note routing - let tag = inputs[8]; - - // Note type: 1 = Public, 2 = Private - let note_type = inputs[9]; - - // Call the bank account to withdraw the assets - bank_account::withdraw(depositor, withdraw_asset, serial_num, tag, note_type); - } -} -``` - -### Note Input Layout - -The withdraw-request-note expects 10 Felt inputs: - -```text -Note Inputs (10 Felts): -┌───────────────────────────────────────────────────────────────────────────┐ -│ Index │ Value │ Description │ -├───────┼─────────────────┼─────────────────────────────────────────────────┤ -│ 0 │ amount │ Token amount to withdraw │ -│ 1 │ 0 │ Reserved (always 0 for fungible) │ -│ 2 │ faucet_suffix │ Faucet ID suffix (identifies asset type) │ -│ 3 │ faucet_prefix │ Faucet ID prefix (identifies asset type) │ -│ 4-7 │ serial_num │ Unique ID for the output P2ID note (4 Felts) │ -│ 8 │ tag │ Note routing tag for P2ID note │ -│ 9 │ note_type │ 1 (Public) or 2 (Private) │ -└───────────────────────────────────────────────────────────────────────────┘ -``` - -:::note Why the Asset is in Inputs -Unlike the deposit note which gets assets from `active_note::get_assets()`, the withdraw request note doesn't carry assets. Instead, the asset to withdraw is specified in the note inputs. The bank then withdraws from its own vault based on these inputs. -::: - -## Step 6: Build All Components - -Build in dependency order: - -```bash title=">_ Terminal" -# 1. Build the account component (generates WIT files) -cd contracts/bank-account -miden build - -# 2. Build the withdraw request note -cd ../withdraw-request-note -miden build -``` - -## Try It: Verify Withdrawals Work - -Let's test the complete withdraw flow. This test: -1. Creates a bank account and initializes it -2. Creates a deposit note and processes it -3. Creates a withdraw-request note with the 10-Felt input layout -4. Processes the withdrawal and verifies a P2ID output note is created - -```rust title="integration/tests/part7_withdraw_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, create_testing_note_from_package, - AccountCreationConfig, NoteCreationConfig, -}; -use miden_client::{ - account::{StorageMap, StorageSlotName}, - asset::{Asset, FungibleAsset}, - note::{build_p2id_recipient, Note, NoteAssets, NoteMetadata, NoteTag, NoteType}, - transaction::{OutputNote, TransactionScript}, - Felt, Word, -}; -use miden_testing::{Auth, MockChain}; -use std::{path::Path, sync::Arc}; - -#[tokio::test] -async fn test_withdraw_creates_p2id_note() -> anyhow::Result<()> { - // ========================================================================= - // SETUP - // ========================================================================= - let mut builder = MockChain::builder(); - - let deposit_amount: u64 = 1000; - - // Create faucet and sender (depositor) - let faucet = - builder.add_existing_basic_faucet(Auth::BasicAuth, "TEST", deposit_amount, Some(10))?; - let sender = builder.add_existing_wallet_with_assets( - Auth::BasicAuth, - [FungibleAsset::new(faucet.id(), deposit_amount)?.into()], - )?; - - // Build contracts - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - let deposit_note_package = Arc::new(build_project_in_dir( - Path::new("../contracts/deposit-note"), - true, - )?); - let init_tx_script_package = Arc::new(build_project_in_dir( - Path::new("../contracts/init-tx-script"), - true, - )?); - let withdraw_request_note_package = Arc::new(build_project_in_dir( - Path::new("../contracts/withdraw-request-note"), - true, - )?); - - // Create bank account with named storage slots - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - miden_client::account::StorageSlot::with_value(initialized_slot, Word::default()), - miden_client::account::StorageSlot::with_map( - balances_slot, - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - let mut bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - - // Create deposit note - let fungible_asset = FungibleAsset::new(faucet.id(), deposit_amount)?; - let note_assets = NoteAssets::new(vec![Asset::Fungible(fungible_asset)])?; - let deposit_note = create_testing_note_from_package( - deposit_note_package.clone(), - sender.id(), - NoteCreationConfig { - assets: note_assets, - ..Default::default() - }, - )?; - - // Add accounts and notes to builder - builder.add_account(bank_account.clone())?; - builder.add_output_note(OutputNote::Full(deposit_note.clone())); - - // ========================================================================= - // CRAFT WITHDRAW REQUEST NOTE (10-Felt input layout) - // ========================================================================= - let withdraw_amount = deposit_amount / 2; - - // Compute P2ID tag for the sender - let p2id_tag = NoteTag::with_account_target(sender.id()); - let p2id_tag_felt = Felt::new(p2id_tag.as_u32() as u64); - - // Serial number for output note - let p2id_output_note_serial_num = Word::from([ - Felt::new(0x1234567890abcdef), - Felt::new(0xfedcba0987654321), - Felt::new(0xdeadbeefcafebabe), - Felt::new(0x0123456789abcdef), - ]); - - let note_type_felt = Felt::new(1); // Public - - // Note inputs: 10 Felts - // [0-3]: withdraw asset (amount, 0, faucet_suffix, faucet_prefix) - // [4-7]: serial_num - // [8]: tag - // [9]: note_type - let withdraw_request_note_inputs = vec![ - Felt::new(withdraw_amount), - Felt::new(0), - faucet.id().suffix(), - faucet.id().prefix().as_felt(), - p2id_output_note_serial_num[0], - p2id_output_note_serial_num[1], - p2id_output_note_serial_num[2], - p2id_output_note_serial_num[3], - p2id_tag_felt, - note_type_felt, - ]; - - let withdraw_request_note = create_testing_note_from_package( - withdraw_request_note_package.clone(), - sender.id(), - NoteCreationConfig { - inputs: withdraw_request_note_inputs, - ..Default::default() - }, - )?; - - builder.add_output_note(OutputNote::Full(withdraw_request_note.clone())); - - // ========================================================================= - // EXECUTE: Initialize, Deposit, Withdraw - // ========================================================================= - let mut mock_chain = builder.build()?; - - // Initialize bank - let init_program = init_tx_script_package.unwrap_program(); - let init_tx_script = TransactionScript::new((*init_program).clone()); - let init_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[], &[])? - .tx_script(init_tx_script) - .build()?; - let executed_init = init_tx_context.execute().await?; - bank_account.apply_delta(&executed_init.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_init)?; - mock_chain.prove_next_block()?; - - println!("Step 1: Bank initialized"); - - // Process deposit - let deposit_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])? - .build()?; - let executed_deposit = deposit_tx_context.execute().await?; - bank_account.apply_delta(&executed_deposit.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_deposit)?; - mock_chain.prove_next_block()?; - - println!("Step 2: Deposited {} tokens", deposit_amount); - - // Process withdraw with expected P2ID output note - let recipient = build_p2id_recipient(sender.id(), p2id_output_note_serial_num)?; - let p2id_output_note_asset = FungibleAsset::new(faucet.id(), withdraw_amount)?; - let p2id_output_note_assets = NoteAssets::new(vec![p2id_output_note_asset.into()])?; - let p2id_output_note_metadata = NoteMetadata::new( - bank_account.id(), - NoteType::Public, - p2id_tag, - ); - let p2id_output_note = Note::new( - p2id_output_note_assets, - p2id_output_note_metadata, - recipient, - ); - - let withdraw_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[withdraw_request_note.id()], &[])? - .extend_expected_output_notes(vec![OutputNote::Full(p2id_output_note)]) - .build()?; - let executed_withdraw = withdraw_tx_context.execute().await?; - bank_account.apply_delta(&executed_withdraw.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_withdraw)?; - mock_chain.prove_next_block()?; - - println!("Step 3: Withdrew {} tokens", withdraw_amount); - println!("\nPart 7 withdraw test passed!"); - - Ok(()) -} -``` - -Run the test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_withdraw_creates_p2id_note -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part7_withdraw_test.rs - -running 1 test -Step 1: Bank initialized -Step 2: Deposited 1000 tokens -Step 3: Withdrew 500 tokens - -Part 7 withdraw test passed! -test test_withdraw_creates_p2id_note ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -:::tip Troubleshooting -**"Insufficient balance for withdrawal"**: Make sure the deposit was processed before attempting withdrawal. - -**"Missing expected output note"**: Verify the P2ID note parameters (tag, serial_num, etc.) match exactly. -::: - -## What We've Built So Far - -| Component | Status | Description | -|-----------|--------|-------------| -| `bank-account` | ✅ Complete | Full deposit AND withdraw logic | -| `deposit-note` | ✅ Complete | Note script for deposits | -| `withdraw-request-note` | ✅ Complete | Note script for withdrawals | -| `init-tx-script` | ✅ Complete | Transaction script for initialization | - -## Complete Code for This Part - -
-Click to see the complete withdraw-request-note code - -```rust title="contracts/withdraw-request-note/src/lib.rs" -// Do not link against libstd (i.e. anything defined in `std::`) -#![no_std] -#![feature(alloc_error_handler)] - -use miden::*; - -// Import the bank account's generated bindings -use crate::bindings::miden::bank_account::bank_account; - -/// Withdraw Request Note Script -/// -/// When consumed by the Bank account, this note requests a withdrawal and -/// the bank creates a P2ID note to send assets back to the depositor. -/// -/// # Note Inputs (10 Felts) -/// [0-3]: withdraw asset (amount, 0, faucet_suffix, faucet_prefix) -/// [4-7]: serial_num (random/unique per note) -/// [8]: tag (P2ID note tag for routing) -/// [9]: note_type (1 = Public, 2 = Private) -#[note] -struct WithdrawRequestNote; - -#[note] -impl WithdrawRequestNote { - #[note_script] - fn run(self, _arg: Word) { - // The depositor is whoever created/sent this note - let depositor = active_note::get_sender(); - - // Get the inputs - let inputs = active_note::get_inputs(); - - // Asset: [amount, 0, faucet_suffix, faucet_prefix] - let withdraw_asset = Asset::new(Word::from([inputs[0], inputs[1], inputs[2], inputs[3]])); - - // Serial number: full 4 Felts (random/unique per note) - let serial_num = Word::from([inputs[4], inputs[5], inputs[6], inputs[7]]); - - // Tag: single Felt for P2ID note routing - let tag = inputs[8]; - - // Note type: 1 = Public, 2 = Private - let note_type = inputs[9]; - - // Call the bank account to withdraw the assets - bank_account::withdraw(depositor, withdraw_asset, serial_num, tag, note_type); - } -} -``` - -
- -## Key Takeaways - -1. **`Recipient::compute()`** creates a cryptographic commitment from serial number, script root, and inputs -2. **`output_note::create()`** creates the note with tag, note type, and recipient -3. **`output_note::add_asset()`** attaches assets to the created note -4. **P2ID pattern** uses a standard script with account ID as input -5. **Serial numbers** must be unique to prevent note replay -6. **Array ordering** - P2ID expects `[suffix, prefix, ...]` not `[prefix, suffix, ...]` -7. **Always validate before subtraction** to prevent underflow exploits - -:::tip View Complete Source -See the complete implementation in the [miden-bank repository](https://github.com/keinberger/miden-bank). -::: - -## Next Steps - -Now that you've built all the components, let's see how they work together in [Part 8: Complete Flows](./complete-flows). diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/08-complete-flows.md b/docs/builder/tutorials/rust-compiler/miden-bank/08-complete-flows.md deleted file mode 100644 index 9abe925f..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/08-complete-flows.md +++ /dev/null @@ -1,585 +0,0 @@ ---- -sidebar_position: 8 -title: "Part 8: Complete Flows" -description: "Walk through end-to-end deposit and withdrawal flows, understanding how all the pieces work together in the banking application." ---- - -# Part 8: Complete Flows - -In this final section, we'll bring everything together and walk through the complete deposit and withdrawal flows, verifying that all the components work as a unified banking system. - -## What You'll Build in This Part - -By the end of this section, you will have: - -- Understood the complete deposit flow from note creation to balance update -- Understood the complete withdraw flow including P2ID note creation -- **Verified the entire system works** with an end-to-end MockChain test -- Completed the Miden Bank tutorial! 🎉 - -## Building on Parts 0-7 - -You've built all the pieces. Now let's see them work together: - -```text -┌────────────────────────────────────────────────────────────────┐ -│ COMPLETE BANK SYSTEM │ -├────────────────────────────────────────────────────────────────┤ -│ │ -│ Components Built: │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ bank-account │ Storage + deposit() + withdraw() │ │ -│ ├─────────────────┼───────────────────────────────────────┤ │ -│ │ deposit-note │ Note script → bank_account::deposit() │ │ -│ ├─────────────────┼───────────────────────────────────────┤ │ -│ │ withdraw-note │ Note script → bank_account::withdraw() │ │ -│ ├─────────────────┼───────────────────────────────────────┤ │ -│ │ init-tx-script │ Transaction script → initialize() │ │ -│ └─────────────────┴───────────────────────────────────────┘ │ -│ │ -│ Storage Layout: │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ initialized (Value) │ Word: [1, 0, 0, 0] when ready│ │ -│ │ balances (StorageMap) │ Map: user_key → [balance, 0, 0, 0]│ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -└────────────────────────────────────────────────────────────────┘ -``` - -## The Complete Deposit Flow - -Let's trace through exactly what happens when a user deposits tokens: - -```text -┌─────────────────────────────────────────────────────────────────────┐ -│ DEPOSIT FLOW │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. USER CREATES DEPOSIT NOTE │ -│ ┌──────────────────────┐ │ -│ │ Deposit Note │ │ -│ │ sender: User │ │ -│ │ assets: [1000 tok] │ │ -│ │ script: deposit-note│ │ -│ │ target: Bank │ │ -│ └──────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 2. BANK CONSUMES NOTE (Transaction begins) │ -│ ┌──────────────────────┐ │ -│ │ Bank Account │ │ -│ │ vault += 1000 tokens│ ◀── Protocol adds assets to vault │ -│ └──────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 3. NOTE SCRIPT EXECUTES │ -│ depositor = active_note::get_sender() → User's AccountId │ -│ assets = active_note::get_assets() → [1000 tokens] │ -│ for asset in assets: │ -│ bank_account::deposit(depositor, asset) ◀── Cross-component│ -│ │ │ -│ ▼ │ -│ 4. DEPOSIT METHOD RUNS (in bank-account context) │ -│ ┌──────────────────────────────────────────┐ │ -│ │ require_initialized() ✓ Passes │ │ -│ │ amount <= MAX_DEPOSIT ✓ 1000 <= 100k │ │ -│ │ native_account::add_asset() ← Confirm │ │ -│ │ balances[User] += 1000 ← Update │ │ -│ └──────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 5. TRANSACTION COMPLETES │ -│ Bank storage: balances[User] = 1000 │ -│ Bank vault: +1000 tokens │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -## The Complete Withdraw Flow - -Now let's trace the withdrawal process: - -```text -┌─────────────────────────────────────────────────────────────────────┐ -│ WITHDRAW FLOW │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. USER CREATES WITHDRAW REQUEST NOTE │ -│ ┌──────────────────────────────┐ │ -│ │ Withdraw Request Note │ │ -│ │ sender: User │ │ -│ │ inputs: [serial, tag, │ │ -│ │ note_type] │ │ -│ │ assets: [withdraw amount] │ │ -│ │ target: Bank │ │ -│ └──────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 2. BANK CONSUMES REQUEST (Transaction begins) │ -│ ┌──────────────────────────────┐ │ -│ │ Note script executes: │ │ -│ │ sender = get_sender() │ │ -│ │ inputs = get_inputs() │ │ -│ │ asset = Asset from inputs │ │ -│ │ bank_account::withdraw(...) │ │ -│ └──────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 3. WITHDRAW METHOD RUNS │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ require_initialized() ✓ Passes │ │ -│ │ current_balance = get_balance(User) → 1000 │ │ -│ │ VALIDATE: 1000 >= 400 ✓ Passes │ ◀ CRITICAL -│ │ balances[User] = 1000 - 400 → 600 │ │ -│ │ create_p2id_note(...) → Output note │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 4. P2ID NOTE CREATED (inside create_p2id_note) │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ script_root = p2id_note_root() → MAST digest │ │ -│ │ recipient = Recipient::compute( │ │ -│ │ serial_num, script_root, │ │ -│ │ [user.suffix, user.prefix] │ │ -│ │ ) │ │ -│ │ note_idx = output_note::create(tag, note_type, │ │ -│ │ recipient) │ │ -│ │ native_account::remove_asset(400 tokens) │ │ -│ │ output_note::add_asset(400 tokens, note_idx) │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 5. TRANSACTION COMPLETES │ -│ Bank storage: balances[User] = 600 │ -│ Bank vault: -400 tokens │ -│ Output: P2ID note with 400 tokens → User │ -│ │ │ -│ ▼ │ -│ 6. USER CONSUMES P2ID NOTE (separate transaction) │ -│ User's wallet receives 400 tokens │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -## Try It: Complete End-to-End Test - -Let's create a comprehensive test that exercises the entire bank system: - -```rust title="integration/tests/part8_complete_flow_test.rs" -use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, create_testing_note_from_package, - AccountCreationConfig, NoteCreationConfig, -}; -use miden_client::{ - account::{StorageMap, StorageSlot, StorageSlotName}, - asset::{Asset, FungibleAsset}, - note::{build_p2id_recipient, Note, NoteAssets, NoteMetadata, NoteTag, NoteType}, - transaction::{OutputNote, TransactionScript}, - Felt, Word, -}; -use miden_testing::{Auth, MockChain}; -use std::{path::Path, sync::Arc}; - -/// Complete end-to-end test of the Miden Bank -/// -/// This test exercises: -/// 1. Bank initialization via transaction script -/// 2. Deposit via deposit-note -/// 3. Withdrawal via withdraw-request-note -/// 4. Balance verification at each step -#[tokio::test] -async fn test_complete_bank_flow() -> anyhow::Result<()> { - println!("╔══════════════════════════════════════════════════════════════╗"); - println!("║ MIDEN BANK - COMPLETE FLOW TEST ║"); - println!("╚══════════════════════════════════════════════════════════════╝"); - - // ═══════════════════════════════════════════════════════════════════ - // SETUP - // ═══════════════════════════════════════════════════════════════════ - println!("\n📦 Setting up test environment..."); - - let mut builder = MockChain::builder(); - - let deposit_amount: u64 = 1000; - let withdraw_amount: u64 = 400; - - // Create a faucet to mint test assets - let faucet = - builder.add_existing_basic_faucet(Auth::BasicAuth, "TEST", deposit_amount, Some(10))?; - - // Create note sender account (the depositor) - let sender = builder.add_existing_wallet_with_assets( - Auth::BasicAuth, - [FungibleAsset::new(faucet.id(), deposit_amount)?.into()], - )?; - println!(" ✓ Faucet and sender wallet created"); - - // Build all packages - let bank_package = Arc::new(build_project_in_dir( - Path::new("../contracts/bank-account"), - true, - )?); - let deposit_note_package = Arc::new(build_project_in_dir( - Path::new("../contracts/deposit-note"), - true, - )?); - let init_tx_script_package = Arc::new(build_project_in_dir( - Path::new("../contracts/init-tx-script"), - true, - )?); - let withdraw_request_note_package = Arc::new(build_project_in_dir( - Path::new("../contracts/withdraw-request-note"), - true, - )?); - println!(" ✓ All packages built"); - - // Create named storage slots - let initialized_slot = - StorageSlotName::new("miden::component::miden_bank_account::initialized") - .expect("Valid slot name"); - let balances_slot = - StorageSlotName::new("miden::component::miden_bank_account::balances") - .expect("Valid slot name"); - - // Create bank account with storage slots - let bank_cfg = AccountCreationConfig { - storage_slots: vec![ - StorageSlot::with_value(initialized_slot, Word::default()), - StorageSlot::with_map( - balances_slot.clone(), - StorageMap::with_entries([]).expect("Empty storage map"), - ), - ], - ..Default::default() - }; - let mut bank_account = - create_testing_account_from_package(bank_package.clone(), bank_cfg).await?; - println!(" ✓ Bank account created: {:?}", bank_account.id()); - - // Create deposit note with assets - let fungible_asset = FungibleAsset::new(faucet.id(), deposit_amount)?; - let note_assets = NoteAssets::new(vec![Asset::Fungible(fungible_asset)])?; - let deposit_note = create_testing_note_from_package( - deposit_note_package.clone(), - sender.id(), - NoteCreationConfig { - assets: note_assets, - ..Default::default() - }, - )?; - - // Craft withdraw request note with 10-Felt input layout - let p2id_tag = NoteTag::with_account_target(sender.id()); - let p2id_tag_felt = Felt::new(p2id_tag.as_u32() as u64); - - let p2id_output_note_serial_num = Word::from([ - Felt::new(0x1234567890abcdef), - Felt::new(0xfedcba0987654321), - Felt::new(0xdeadbeefcafebabe), - Felt::new(0x0123456789abcdef), - ]); - - let note_type_felt = Felt::new(1); // Public - - // Note inputs: 10 Felts - // [0-3]: withdraw asset (amount, 0, faucet_suffix, faucet_prefix) - // [4-7]: serial_num - // [8]: tag - // [9]: note_type - let withdraw_request_note_inputs = vec![ - Felt::new(withdraw_amount), - Felt::new(0), - faucet.id().suffix(), - faucet.id().prefix().as_felt(), - p2id_output_note_serial_num[0], - p2id_output_note_serial_num[1], - p2id_output_note_serial_num[2], - p2id_output_note_serial_num[3], - p2id_tag_felt, - note_type_felt, - ]; - - let withdraw_request_note = create_testing_note_from_package( - withdraw_request_note_package.clone(), - sender.id(), - NoteCreationConfig { - inputs: withdraw_request_note_inputs, - ..Default::default() - }, - )?; - - // Add to builder - builder.add_account(bank_account.clone())?; - builder.add_output_note(OutputNote::Full(deposit_note.clone())); - builder.add_output_note(OutputNote::Full(withdraw_request_note.clone())); - - let mut mock_chain = builder.build()?; - println!(" ✓ MockChain built"); - - // ═══════════════════════════════════════════════════════════════════ - // STEP 1: Initialize the bank - // ═══════════════════════════════════════════════════════════════════ - println!("\n1️⃣ INITIALIZING BANK..."); - - let init_program = init_tx_script_package.unwrap_program(); - let init_tx_script = TransactionScript::new((*init_program).clone()); - - let init_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[], &[])? - .tx_script(init_tx_script) - .build()?; - - let executed_init = init_tx_context.execute().await?; - bank_account.apply_delta(&executed_init.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_init)?; - mock_chain.prove_next_block()?; - - println!(" ✓ Bank initialized (storage[0] = [1, 0, 0, 0])"); - - // ═══════════════════════════════════════════════════════════════════ - // STEP 2: Deposit tokens - // ═══════════════════════════════════════════════════════════════════ - println!("\n2️⃣ DEPOSITING TOKENS..."); - println!(" Deposit amount: {} tokens", deposit_amount); - - let deposit_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])? - .build()?; - - let executed_deposit = deposit_tx_context.execute().await?; - bank_account.apply_delta(&executed_deposit.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_deposit)?; - mock_chain.prove_next_block()?; - - // Verify balance after deposit - let depositor_key = Word::from([ - sender.id().prefix().as_felt(), - sender.id().suffix(), - faucet.id().prefix().as_felt(), - faucet.id().suffix(), - ]); - let balance_after_deposit = bank_account.storage().get_map_item(&balances_slot, depositor_key)?; - println!( - " ✓ Bank processed deposit, balance: {} tokens", - balance_after_deposit[3].as_int() - ); - - // ═══════════════════════════════════════════════════════════════════ - // STEP 3: Withdraw tokens - // ═══════════════════════════════════════════════════════════════════ - println!("\n3️⃣ WITHDRAWING TOKENS..."); - println!(" Withdraw amount: {} tokens", withdraw_amount); - - // Build expected P2ID output note - let recipient = build_p2id_recipient(sender.id(), p2id_output_note_serial_num)?; - let p2id_output_note_asset = FungibleAsset::new(faucet.id(), withdraw_amount)?; - let p2id_output_note_assets = NoteAssets::new(vec![p2id_output_note_asset.into()])?; - let p2id_output_note_metadata = NoteMetadata::new( - bank_account.id(), - NoteType::Public, - p2id_tag, - ); - let p2id_output_note = Note::new( - p2id_output_note_assets, - p2id_output_note_metadata, - recipient, - ); - - let withdraw_tx_context = mock_chain - .build_tx_context(bank_account.id(), &[withdraw_request_note.id()], &[])? - .extend_expected_output_notes(vec![OutputNote::Full(p2id_output_note)]) - .build()?; - - let executed_withdraw = withdraw_tx_context.execute().await?; - bank_account.apply_delta(&executed_withdraw.account_delta())?; - mock_chain.add_pending_executed_transaction(&executed_withdraw)?; - mock_chain.prove_next_block()?; - - println!(" ✓ Bank processed withdraw request"); - println!(" ✓ P2ID output note created for sender"); - - // Verify final balance - let final_balance = bank_account.storage().get_map_item(&balances_slot, depositor_key)?; - let final_balance_amount = final_balance[3].as_int(); - let expected_final = deposit_amount - withdraw_amount; - - println!(" ✓ Final balance verified: {} tokens", final_balance_amount); - - // ═══════════════════════════════════════════════════════════════════ - // SUMMARY - // ═══════════════════════════════════════════════════════════════════ - println!("\n╔══════════════════════════════════════════════════════════════╗"); - println!("║ TEST SUMMARY ║"); - println!("╠══════════════════════════════════════════════════════════════╣"); - println!( - "║ Initial deposit: {:>6} tokens ║", - deposit_amount - ); - println!( - "║ Withdrawal: -{:>6} tokens ║", - withdraw_amount - ); - println!( - "║ Final balance: {:>6} tokens ║", - final_balance_amount - ); - println!("║ ║"); - println!("║ ✅ All operations completed successfully! ║"); - println!("╚══════════════════════════════════════════════════════════════╝"); - - assert_eq!( - final_balance_amount, expected_final, - "Final balance should be deposit - withdraw" - ); - - Ok(()) -} -``` - -Run the complete test from the project root: - -```bash title=">_ Terminal" -cargo test --package integration test_complete_bank_flow -- --nocapture -``` - -
-Expected output - -```text - Compiling integration v0.1.0 (/path/to/miden-bank/integration) - Finished `test` profile [unoptimized + debuginfo] target(s) - Running tests/part8_complete_flow_test.rs - -running 1 test -╔══════════════════════════════════════════════════════════════╗ -║ MIDEN BANK - COMPLETE FLOW TEST ║ -╚══════════════════════════════════════════════════════════════╝ - -📦 Setting up test environment... - ✓ Faucet and sender wallet created - ✓ All packages built - ✓ Bank account created: 0x... - ✓ MockChain built - -1️⃣ INITIALIZING BANK... - ✓ Bank initialized (storage[0] = [1, 0, 0, 0]) - -2️⃣ DEPOSITING TOKENS... - Deposit amount: 1000 tokens - ✓ Bank processed deposit, balance: 1000 tokens - -3️⃣ WITHDRAWING TOKENS... - Withdraw amount: 400 tokens - ✓ Bank processed withdraw request - ✓ P2ID output note created for sender - ✓ Final balance verified: 600 tokens - -╔══════════════════════════════════════════════════════════════╗ -║ TEST SUMMARY ║ -╠══════════════════════════════════════════════════════════════╣ -║ Initial deposit: 1000 tokens ║ -║ Withdrawal: - 400 tokens ║ -║ Final balance: 600 tokens ║ -║ ║ -║ ✅ All operations completed successfully! ║ -╚══════════════════════════════════════════════════════════════╝ -test test_complete_bank_flow ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored -``` - -
- -## Summary: All Components - -Here's the complete picture of what you've built: - -| Component | Type | Purpose | -|-----------|------|---------| -| `bank-account` | Account Component | Manages balances and vault | -| `deposit-note` | Note Script | Processes incoming deposits | -| `withdraw-request-note` | Note Script | Requests withdrawals | -| `init-tx-script` | Transaction Script | Initializes the bank | - -| Storage Slot | Type | Content | -|--------------|------|---------| -| `initialized` | `Value` | Initialization flag | -| `balances` | `StorageMap` | Depositor balances | - -| API | Purpose | -|-----|---------| -| `active_note::get_sender()` | Identify note creator | -| `active_note::get_assets()` | Get attached assets | -| `active_note::get_inputs()` | Get note parameters | -| `native_account::add_asset()` | Receive into vault | -| `native_account::remove_asset()` | Send from vault | -| `output_note::create()` | Create output note | -| `output_note::add_asset()` | Attach assets to note | - -## Key Security Patterns - -Remember these critical patterns from this tutorial: - -:::danger Always Validate Before Subtraction -```rust -// ❌ DANGEROUS: Silent underflow! -let new_balance = current_balance - withdraw_amount; - -// ✅ SAFE: Validate first -assert!( - current_balance.as_u64() >= withdraw_amount.as_u64(), - "Insufficient balance" -); -let new_balance = Felt::from_u64_unchecked( - current_balance.as_u64() - withdraw_amount.as_u64() -); -``` -::: - -:::warning Felt Comparison Operators -Never use `<`, `>` on Felt values directly. Always convert to u64 first: -```rust -// ❌ BROKEN: Produces incorrect results -if current_balance < withdraw_amount { ... } - -// ✅ CORRECT: Use as_u64() -if current_balance.as_u64() < withdraw_amount.as_u64() { ... } -``` -::: - -## Congratulations! 🎉 - -You've completed the Miden Bank tutorial! You now understand: - -- ✅ **Account components** with storage (`Value` and `StorageMap`) -- ✅ **Constants and constraints** for business rules -- ✅ **Asset management** with vault operations -- ✅ **Note scripts** for processing incoming notes -- ✅ **Cross-component calls** via generated bindings -- ✅ **Transaction scripts** for owner operations -- ✅ **Output notes** for sending assets (P2ID pattern) -- ✅ **Security patterns** for safe arithmetic - -### Continue Learning - -- **[Testing with MockChain](../testing)** - Deep dive into testing patterns -- **[Debugging Guide](../debugging)** - Troubleshoot common issues -- **[Common Pitfalls](../pitfalls)** - Avoid known gotchas - -### Build More - -Use these patterns to build: -- Token faucets -- DEX contracts -- NFT marketplaces -- Multi-signature wallets -- And more! - -:::tip View Complete Source -Explore the complete banking application: -- [All Contracts](https://github.com/keinberger/miden-bank/tree/main/contracts) -- [Integration Tests](https://github.com/keinberger/miden-bank/tree/main/integration/tests) -- [Test Helpers](https://github.com/keinberger/miden-bank/blob/main/integration/src/helpers.rs) -::: - -Happy building on Miden! 🚀 diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/_category_.json b/docs/builder/tutorials/rust-compiler/miden-bank/_category_.json deleted file mode 100644 index b4a64e58..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Miden Bank", - "position": 1, - "link": { - "type": "doc", - "id": "builder/tutorials/rust-compiler/miden-bank/index" - } -} diff --git a/docs/builder/tutorials/rust-compiler/miden-bank/index.md b/docs/builder/tutorials/rust-compiler/miden-bank/index.md deleted file mode 100644 index ac9f8ef3..00000000 --- a/docs/builder/tutorials/rust-compiler/miden-bank/index.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -sidebar_position: 4 -title: "Building a Bank with Miden Rust" -description: "Learn Miden Rust compiler fundamentals by building a complete banking application with deposits, withdrawals, and asset management." ---- - -# Building a Bank with Miden Rust - -Welcome to the **Miden Rust Compiler Tutorial**! This hands-on guide teaches you how to build smart contracts on Miden using Rust by walking through a complete banking application. - -## What You'll Build - -You'll create a **banking system** consisting of: - -- **Bank Account Component**: A smart contract that manages depositor balances and vault operations -- **Deposit Note**: A note script that processes deposits into the bank -- **Withdraw Request Note**: A note script that requests withdrawals from the bank -- **Initialization Script**: A transaction script to deploy and initialize the bank - -Each part ends with a **runnable MockChain test** that verifies what you built works correctly. - -## Tutorial Structure - -This tutorial is designed for hands-on learning. Each part builds on the previous one, and every part includes: - -- **What You'll Build** - Clear objectives for the section -- **Step-by-step code** - Progressively building functionality -- **Try It section** - A MockChain test to verify your code works -- **Complete code** - Full code listing for reference - -### Parts Overview - -| Part | Topic | What You'll Build | -|------|-------|-------------------| -| **Part 0** | [Project Setup](./project-setup) | Create project with `miden new` | -| **Part 1** | [Account Components](./account-components) | Bank struct with storage | -| **Part 2** | [Constants & Constraints](./constants-constraints) | Business rules and validation | -| **Part 3** | [Asset Management](./asset-management) | Deposit logic with balance tracking | -| **Part 4** | [Note Scripts](./note-scripts) | Deposit note for receiving assets | -| **Part 5** | [Cross-Component Calls](./cross-component-calls) | How bindings enable calls | -| **Part 6** | [Transaction Scripts](./transaction-scripts) | Initialization script | -| **Part 7** | [Output Notes](./output-notes) | Withdraw with P2ID output | -| **Part 8** | [Complete Flows](./complete-flows) | End-to-end verification | - -## Tutorial Cards - -import DocCard from '@theme/DocCard'; - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -## Prerequisites - -Before starting this tutorial, ensure you have: - -- Completed the [Get Started guide](../../../get-started/) (familiarity with `midenup`, `miden new`, basic tooling) -- Basic understanding of Miden concepts (accounts, notes, transactions) -- Rust programming experience - -:::tip No Miden Rust Experience Required -This tutorial assumes no prior experience with the Miden Rust compiler. We'll explain each concept as we encounter it. -::: - -## Concepts Covered - -This tutorial covers the following Miden Rust compiler features: - -| Concept | Description | Part | -| --------------------- | ---------------------------------------------------------- | ---- | -| `#[component]` | Define account components with storage | 1 | -| Storage Types | `Value` for single values, `StorageMap` for key-value data | 1 | -| Constants | Define compile-time business rules | 2 | -| Assertions | Validate conditions and handle errors | 2 | -| Asset Handling | Add and remove assets from account vaults | 3 | -| `#[note]` + `#[note_script]` | Note struct/impl pattern for scripts consumed by accounts | 4 | -| Cross-Component Calls | Call account methods from note scripts | 5 | -| `#[tx_script]` | Transaction scripts for account operations | 6 | -| Output Notes | Create notes programmatically | 7 | - -## Source Code Repository - -The complete source code for this tutorial is available in the **[miden-bank repository](https://github.com/keinberger/miden-bank)**. You can clone it to follow along or reference the implementation: - -```bash title=">_ Terminal" -git clone https://github.com/keinberger/miden-bank.git -cd miden-bank -``` - -## Supplementary Guides - -These standalone guides complement the tutorial: - -- **[Testing with MockChain](../testing)** - Learn to test your contracts -- **[Debugging](../debugging)** - Troubleshoot common issues -- **[Common Pitfalls](../pitfalls)** - Avoid known gotchas - -## Getting Help - -If you get stuck during this tutorial: - -- Check the [Miden Docs](https://docs.miden.xyz) for detailed technical references -- Join the [Build On Miden](https://t.me/BuildOnMiden) Telegram community for support -- Review the complete code in the [miden-bank repository](https://github.com/keinberger/miden-bank) - -Ready to build your first Miden banking application? Let's get started with [Part 0: Project Setup](./project-setup)! diff --git a/docs/builder/tutorials/rust-compiler/pitfalls.md b/docs/builder/tutorials/rust-compiler/pitfalls.md index 4742d85c..1c34b13a 100644 --- a/docs/builder/tutorials/rust-compiler/pitfalls.md +++ b/docs/builder/tutorials/rust-compiler/pitfalls.md @@ -493,4 +493,4 @@ See these patterns in context in the [miden-bank repository](https://github.com/ - **[Debugging Guide](./debugging)** - Troubleshoot errors - **[Testing Guide](./testing)** - MockChain patterns -- **[Miden Bank Tutorial](./miden-bank/)** - See these patterns in context +- **[Miden Bank Tutorial](../miden-bank/)** - See these patterns in context diff --git a/docs/builder/tutorials/rust-compiler/testing.md b/docs/builder/tutorials/rust-compiler/testing.md index eb089822..ff2bdc58 100644 --- a/docs/builder/tutorials/rust-compiler/testing.md +++ b/docs/builder/tutorials/rust-compiler/testing.md @@ -607,4 +607,4 @@ See the complete test implementations in the [miden-bank repository](https://git - **[Debugging Guide](./debugging)** - Troubleshoot common issues - **[Common Pitfalls](./pitfalls)** - Avoid known gotchas -- **[Miden Bank Tutorial](./miden-bank/)** - See testing in action +- **[Miden Bank Tutorial](../miden-bank/)** - See testing in action diff --git a/docusaurus.config.ts b/docusaurus.config.ts index ac81dc07..0320e1cb 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -106,6 +106,10 @@ const config: Config = { if (existingPath === "/builder/glossary") { redirects.push("/glossary"); } + if (existingPath.startsWith("/builder/tutorials/miden-bank")) { + redirects.push(existingPath.replace("/builder/tutorials/miden-bank", "/builder/tutorials/rust-compiler/miden-bank")); + redirects.push(existingPath.replace("/builder/tutorials/miden-bank", "/builder/develop/tutorials/rust-compiler/miden-bank")); + } if (existingPath.startsWith("/builder/tutorials")) { redirects.push(existingPath.replace("/builder/tutorials", "/miden-tutorials")); }