diff --git a/CHANGELOG.md b/CHANGELOG.md index a84aa03c2d..f30d36f1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,829 +2,42 @@ ## v0.15.0 (TBD) +### Features + +- Added `FungibleTokenMetadata` component with name, description, logo URI, and external link support, and MASM procedures `get_token_metadata`, `get_max_supply`, `get_decimals`, and `get_token_symbol` for the token metadata standard. ([#2439](https://github.com/0xMiden/miden-base/pull/2439)) +- Aligned fungible faucet token metadata with the metadata standard: faucet now uses the canonical slot `miden::standards::metadata::token_metadata` so MASM metadata getters work with faucet storage. + ### Changes - [BREAKING] Renamed `ProvenBatch::new` to `new_unchecked` ([#2687](https://github.com/0xMiden/miden-base/issues/2687)). +--- + ## 0.14.0 (2026-03-23) ### Features +- Made `NoteMetadataHeader` and `NoteMetadata::to_header()` public, added `NoteMetadata::from_header()` constructor, and exported `NoteMetadataHeader` from the `note` module ([#2561](https://github.com/0xMiden/protocol/pull/2561)). +- Introduce NOTE_MAX_SIZE (256 KiB) and enforce it on individual output notes ([#2205](https://github.com/0xMiden/miden-base/pull/2205), [#2651](https://github.com/0xMiden/miden-base/pull/2651)). +- Added AggLayer faucet registry to bridge account with conversion metadata, `CONFIG_AGG_BRIDGE` note for faucet registration, and FPI-based asset conversion in `bridge_out` ([#2426](https://github.com/0xMiden/miden-base/pull/2426)). - Added single-word `Array` standard ([#2203](https://github.com/0xMiden/miden-base/pull/2203)). - Added `SignedBlock` struct ([#2355](https://github.com/0xMiden/miden-base/pull/2235)). - Enabled `CodeBuilder` to add advice map entries to compiled scripts ([#2275](https://github.com/0xMiden/miden-base/pull/2275)). - Implemented verification of AggLayer deposits (claims) against GER ([#2288](https://github.com/0xMiden/miden-base/pull/2288), [#2295](https://github.com/0xMiden/miden-base/pull/2295)). -- Added `Ownable2Step` account component with two-step ownership transfer (`transfer_ownership`, `accept_ownership`, `renounce_ownership`) and `owner`, `nominated_owner` procedures ([#2292](https://github.com/0xMiden/miden-base/pull/2292)). -- Added double-word array data structure abstraction over storage maps ([#2299](https://github.com/0xMiden/miden-base/pull/2299)). -- Added `BlockNumber::MAX` constant to represent the maximum block number ([#2324](https://github.com/0xMiden/miden-base/pull/2324)). -- Introduced `TokenMetadata` type to encapsulate fungible faucet metadata ([#2344](https://github.com/0xMiden/miden-base/issues/2344)). -- Added `PackageKind` and `ProcedureExport` ([#2358](https://github.com/0xMiden/miden-base/pull/2358)). -- Added `AccountTargetNetworkNote` type and `NetworkNoteExt` trait with `is_network_note()` / `as_account_target_network_note()` helpers ([#2365](https://github.com/0xMiden/miden-base/pull/2365)). -- [BREAKING] Added `get_asset` and `get_initial_asset` kernel procedures and removed `get_balance`, `get_initial_balance` and `has_non_fungible_asset` kernel procedures ([#2369](https://github.com/0xMiden/miden-base/pull/2369)). -- Added `p2id::new` MASM constructor procedure for creating P2ID notes from MASM code ([#2381](https://github.com/0xMiden/miden-base/pull/2381)). -- Implemented `assert_valid_ger` procedure for verifying GER against storage ([#2388](https://github.com/0xMiden/miden-base/pull/2388)). -- Added `P2idNoteStorage` and `P2ideNoteStorage` ([#2389](https://github.com/0xMiden/miden-base/pull/2389)). -- Added `StandardNote::from_script_root()` and `StandardNote::name()` methods, and exposed `NoteType` `PUBLIC`/`PRIVATE` masks as public constants ([#2411](https://github.com/0xMiden/miden-base/pull/2411)). -- Resolve standard note scripts directly in `TransactionExecutorHost` instead of querying the data store ([#2417](https://github.com/0xMiden/miden-base/pull/2417)). -- Added AggLayer faucet registry to bridge account with conversion metadata, `CONFIG_AGG_BRIDGE` note for faucet registration, and FPI-based asset conversion in `bridge_out` ([#2426](https://github.com/0xMiden/miden-base/pull/2426)). -- Added `DEFAULT_TAG` constant to `miden::standards::note_tag` MASM module ([#2482](https://github.com/0xMiden/miden-base/pull/2482)). -- Added `NoteExecutionHint` variant constants (`NONE`, `ALWAYS`, `AFTER_BLOCK`, `ON_BLOCK_SLOT`) to `miden::standards::note::execution_hint` MASM module ([#2493](https://github.com/0xMiden/miden-base/pull/2493)). -- Added `Package` support in `MockChainBuilder` & `NoteScript` ([#2502](https://github.com/0xMiden/protocol/pull/2502)). -- Added PSM authentication procedures and integrated them into `AuthMultisig` ([#2527](https://github.com/0xMiden/protocol/pull/2527)). -- Added `CodeBuilder::with_warnings_as_errors()` to promote assembler warning diagnostics to errors ([#2558](https://github.com/0xMiden/protocol/pull/2558)). -- Added `MintPolicyConfig` for flexible minting policy enforcement ([#2559](https://github.com/0xMiden/protocol/pull/2559)) -- Added `MockChain::add_pending_batch()` to allow submitting user batches directly ([#2565](https://github.com/0xMiden/protocol/pull/2565)). -- Implemented the `on_before_asset_added_to_account` asset callback ([#2571](https://github.com/0xMiden/protocol/pull/2571)). -- Added `ProgramExecutor` hooks to support DAP and other custom transaction program executors ([#2574](https://github.com/0xMiden/protocol/pull/2574)). -- Added `create_fungible_key` for construction of fungible asset keys ([#2575](https://github.com/0xMiden/protocol/pull/2575)). -- Added metadata hash storage to AggLayer faucet and FPI retrieval during bridge-out leaf construction ([#2583](https://github.com/0xMiden/protocol/pull/2583)). -- Added `SwapNoteStorage` for typed serialization/deserialization of SWAP note storage ([#2585](https://github.com/0xMiden/protocol/pull/2585)). -- Added `InputNoteCommitment::from_parts()` for construction of input note commitments from a nullifier and optional note header ([#2588](https://github.com/0xMiden/protocol/pull/2588)). -- Added `bool` schema type to the type registry and updated ACL auth component to use it for boolean config fields ([#2591](https://github.com/0xMiden/protocol/pull/2591)). -- Implemented the `on_before_asset_added_to_note` asset callback ([#2595](https://github.com/0xMiden/protocol/pull/2595)). -- Added `component_metadata()` to all account components to expose their metadata ([#2596](https://github.com/0xMiden/protocol/pull/2596)). -- [BREAKING] Changed `native_account::remove_asset` to return the asset value remaining in the vault instead of the removed value ([#2626](https://github.com/0xMiden/protocol/pull/2626)). -- Implemented `TransactionEventId::event_name` and `Host::resolve_event` for better VM diagnostics during even handler failures ([#2628](https://github.com/0xMiden/protocol/pull/2628)). -- Added `FixedWidthString` for fixed-width UTF-8 string storage in `miden-standards` (`miden::standards::utils::string`). ([#2633](https://github.com/0xMiden/protocol/pull/2633)) +- Added `Ownable2Step` account component ([#2292](https://github.com/0xMiden/miden-base/pull/2292)). +- Added `BlockNumber::MAX` constant ([#2324](https://github.com/0xMiden/miden-base/pull/2324)). +- [BREAKING] Added `get_asset` and `get_initial_asset` kernel procedures ([#2369](https://github.com/0xMiden/miden-base/pull/2369)). +- Added `FixedWidthString` for fixed-width UTF-8 string storage ([#2633](https://github.com/0xMiden/protocol/pull/2633)). ### Changes -- [BREAKING] Renamed `NoteInputs` to `NoteStorage` to better reflect that values are stored data associated with a note rather than inputs ([#1662](https://github.com/0xMiden/miden-base/issues/1662), [#2316](https://github.com/0xMiden/miden-base/issues/2316)). -- Introduced NOTE_MAX_SIZE (256 KiB) and enforce it on individual output notes ([#2205](https://github.com/0xMiden/miden-base/pull/2205), [#2651](https://github.com/0xMiden/miden-base/pull/2651)). -- Restructured `miden-agglayer/asm` directory to separate bridge and faucet into per-component libraries, preventing cross-component procedure exposure ([#2294](https://github.com/0xMiden/miden-base/issues/2294)). -- Skip requests to the `DataStore` for asset vault witnesses which are already in transaction inputs ([#2298](https://github.com/0xMiden/miden-base/pull/2298)). -- [BREAKING] Refactored `TransactionAuthenticator::get_public_key()` method to return `Arc `instead of `&PublicKey` ([#2304](https://github.com/0xMiden/miden-base/pull/2304)). -- Removed `NoteType::Encrypted` ([#2315](https://github.com/0xMiden/miden-base/pull/2315)). -- [BREAKING] Updated note tag length to support up to 32 bits ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). -- [BREAKING] Renamed `WellKnownComponent` to `StandardAccountComponent`, `WellKnownNote` to `StandardNote`, and `WellKnownNoteAttachment` to `StandardNoteAttachment` ([#2332](https://github.com/0xMiden/miden-base/pull/2332)). -- Added B2AGG and UPDATE_GER note attachment target checks ([#2334](https://github.com/0xMiden/miden-base/pull/2334)). -- Removed protocol-reserved faucet sysdata storage slot ([#2335](https://github.com/0xMiden/miden-base/pull/2335)). -- [BREAKING] Moved standard note code into individual note modules ([#2363](https://github.com/0xMiden/miden-base/pull/2363)). -- [BREAKING] Prefixed transaction kernel events with `miden::protocol` ([#2364](https://github.com/0xMiden/miden-base/pull/2364)). -- [BREAKING] Added `miden::standards::note_tag` module for account target note tags ([#2366](https://github.com/0xMiden/miden-base/pull/2366)). -- [BREAKING] Made `AccountComponentMetadata` a required parameter of `AccountComponent::new()`; removed `with_supported_type`, `with_supports_all_types`, and `with_metadata` methods from `AccountComponent`; simplified `AccountComponentMetadata::new()` to take just `name`; renamed `AccountComponentTemplateError` to `ComponentMetadataError` ([#2373](https://github.com/0xMiden/miden-base/pull/2373), [#2395](https://github.com/0xMiden/miden-base/pull/2395)). -- [BREAKING] Changed note scripts to be compiled as libraries with `@note_script` annotation for marking the entrypoint procedure ([#2339](https://github.com/0xMiden/miden-base/issues/2339), [#2374](https://github.com/0xMiden/miden-base/pull/2374)). -- Made kernel procedure offset constants public and replaced accessor procedures with direct constant usage ([#2375](https://github.com/0xMiden/miden-base/pull/2375)). -- Removed redundant note storage item count from advice map ([#2376](https://github.com/0xMiden/miden-base/pull/2376)). -- Added `miden::protocol::auth` module with public auth event constants ([#2377](https://github.com/0xMiden/miden-base/pull/2377)). -- Moved `NoteExecutionHint` to `miden-standards` ([#2378](https://github.com/0xMiden/miden-base/pull/2378)). -- [BREAKING] Simplified `NoteMetadata::new()` constructor to not require tag parameter; tag defaults to zero and can be set via `with_tag()` builder method ([#2384](https://github.com/0xMiden/miden-base/pull/2384)). -- Unified the underlying representation of `ExitRoot` and `SmtNode` and use type aliases ([#2387](https://github.com/0xMiden/miden-base/pull/2387)). -- Changed GER storage to a map ([#2388](https://github.com/0xMiden/miden-base/pull/2388)). -- [BREAKING] Consolidated authentication components ([#2390] (https://github.com/0xMiden/miden-base/pull/2390)) -- [BREAKING] Refactored assets in the tx kernel and `miden::protocol` from one to two words, i.e. `ASSET` becomes `ASSET_KEY` and `ASSET_VALUE` ([#2396](https://github.com/0xMiden/miden-base/pull/2396), [#2410](https://github.com/0xMiden/miden-base/pull/2410)). -- Fixed MASM inline comment casing to adhere to commenting conventions ([#2398](https://github.com/0xMiden/miden-base/pull/2398)). -- Prefixed standard account component names with `miden::standards::components` ([#2400](https://github.com/0xMiden/miden-base/pull/2400)). -- Replaced auth event constant workarounds with direct imports now that `miden-assembly` v0.20.6 supports it ([#2404](https://github.com/0xMiden/miden-base/pull/2404)). -- [BREAKING] Moved padding to the end of `CLAIM` `NoteStorage` layout ([#2405](https://github.com/0xMiden/miden-base/pull/2405)). -- [BREAKING] Renamed `miden::protocol::asset::build_fungible_asset` to `miden::protocol::asset::create_fungible_asset` ([#2410](https://github.com/0xMiden/miden-base/pull/2410)). -- [BREAKING] Renamed `miden::protocol::asset::build_non_fungible_asset` to `miden::protocol::asset::create_non_fungible_asset` ([#2410](https://github.com/0xMiden/miden-base/pull/2410)). -- Updated account schema commitment construction to accept borrowed schema iterators; added extension trait to enable `AccountBuilder::with_schema_commitment()` helper ([#2419](https://github.com/0xMiden/miden-base/pull/2419)). -- Increased `TokenSymbol` max allowed length from 6 to 12 uppercase characters ([#2420](https://github.com/0xMiden/miden-base/pull/2420)). -- Introduced `StorageMapKey` and `StorageMapKeyHash` Word wrappers for type-safe storage map key handling ([#2431](https://github.com/0xMiden/miden-base/pull/2431)). -- [BREAKING] Changed the layout of fungible and non-fungible assets ([#2437](https://github.com/0xMiden/miden-base/pull/2437)). -- [BREAKING] Refactored account ID and nonce memory and advice stack layout ([#2442](https://github.com/0xMiden/miden-base/pull/2442)). -- [BREAKING] Removed `hash_account` ([#2442](https://github.com/0xMiden/miden-base/pull/2442)). -- [BREAKING] Renamed `AccountHeader::commitment`, `Account::commitment` and `PartialAccount::commitment` to `to_commitment` ([#2442](https://github.com/0xMiden/miden-base/pull/2442)). -- [BREAKING] Remove `BlockSigner` trait ([#2447](https://github.com/0xMiden/miden-base/pull/2447)). -- [BREAKING] Fixed `TokenSymbol::try_from(Felt)` to reject values below `MIN_ENCODED_VALUE`; implemented `Display` for `TokenSymbol` replacing the fallible `to_string()` method; removed `Default` derive ([#2464](https://github.com/0xMiden/protocol/issues/2464)). -- [BREAKING] Renamed `SchemaTypeId` to `SchemaType` ([#2494](https://github.com/0xMiden/miden-base/pull/2494)). -- Introduced a dedicated AccountIdKey type to unify and centralize all AccountId → SMT and advice-map key conversions ([#2495](https://github.com/0xMiden/miden-base/pull/2495)). -- Updated stale `miden-base` references to `protocol` across docs, READMEs, code comments, and Cargo.toml repository URL ([#2503](https://github.com/0xMiden/protocol/pull/2503)). -- [BREAKING] The native hash function changed from RPO256 to Poseidon2 - see PR description ([#2508](https://github.com/0xMiden/miden-base/pull/2508)). -- [BREAKING] Migrated to miden-vm 0.21 and miden-crypto 0.22 ([#2508](https://github.com/0xMiden/miden-base/pull/2508)). -- [BREAKING] The stack orientation changed from big-endian to little-endian - see PR description ([#2508](https://github.com/0xMiden/miden-base/pull/2508)). -- [BREAKING] Reverse the order of the transaction summary on the stack ([#2512](https://github.com/0xMiden/miden-base/pull/2512)). -- [BREAKING] Use `@auth_script` MASM attribute instead of `auth_` prefix to identify authentication procedures in account components ([#2534](https://github.com/0xMiden/protocol/pull/2534)). -- [BREAKING] Made `supported_types` a required parameter of `AccountComponentMetadata::new()`; removed `with_supported_type`, `with_supported_types`, `with_supports_all_types`, and `with_supports_regular_types` builder methods; added `AccountType::all()` and `AccountType::regular()` helpers ([#2554](https://github.com/0xMiden/protocol/pull/2554)). -- Fixed link map entry pointer validation bypass ([#2556](https://github.com/0xMiden/protocol/pull/2556)). -- Fixed overlap in initial and active account storage slot memory region ([#2557](https://github.com/0xMiden/protocol/pull/2557)). -- [BREAKING] Removed `NoteAssets::add_asset`; `OutputNoteBuilder` now accumulates assets in a `Vec` and computes the commitment only when `build()` is called, avoiding rehashing on every asset addition. ([#2577](https://github.com/0xMiden/protocol/pull/2577)). -- Added foreign account ID assertion in `account::load_foreign_account` ([#2560](https://github.com/0xMiden/protocol/pull/2560)). -- Made `NoteMetadataHeader` and `NoteMetadata::to_header()` public, added `NoteMetadata::from_header()` constructor, and exported `NoteMetadataHeader` from the `note` module ([#2561](https://github.com/0xMiden/protocol/pull/2561)). -- [BREAKING] Removed `ProvenTransactionBuilder` in favor of `ProvenTransaction::new()` constructor ([#2567](https://github.com/0xMiden/miden-base/pull/2567)). -- [BREAKING] Renamed `AccountComponent::get_procedures()` to `procedures()`, returning `impl Iterator` ([#2597](https://github.com/0xMiden/protocol/pull/2597)). - -- Moved `AccountSchemaCommitment` component into a sub-module ([#2603](https://github.com/0xMiden/protocol/pull/2603)). -- [BREAKING] Separated `EthAddress` (plain 20-byte Ethereum address) and `EthEmbeddedAccountId` (Miden AccountId encoded as Ethereum address) into distinct types, replacing the single `EthAddressFormat` struct. ([#2622](https://github.com/0xMiden/protocol/pull/2622)). -- [BREAKING] `miden::protocol::faucet::burn` no longer returns the burnt asset value ([#2626](https://github.com/0xMiden/protocol/pull/2626)). -- [BREAKING] `AssetVault::remove_asset` returns the asset value remaining in the vault `Option` rather than the removed value `Asset` ([#2626](https://github.com/0xMiden/protocol/pull/2626)). -- [BREAKING] Renamed `MMR Frontier` to `Merkle Tree Frontier (MTF)`, module was renamed from `mmr_frontier32_keccak` to `merkle_tree_frontier` ([#2642](https://github.com/0xMiden/protocol/pull/2642)). +- [BREAKING] Renamed `NoteInputs` to `NoteStorage` ([#1662](https://github.com/0xMiden/miden-base/issues/1662)). +- [BREAKING] Renamed `WellKnownComponent` to `StandardAccountComponent`, `WellKnownNote` to `StandardNote` ([#2332](https://github.com/0xMiden/miden-base/pull/2332)). +- [BREAKING] Refactored assets in the tx kernel from one to two words (`ASSET_KEY` and `ASSET_VALUE`) ([#2396](https://github.com/0xMiden/miden-base/pull/2396)). +- [BREAKING] The native hash function changed from RPO256 to Poseidon2 ([#2508](https://github.com/0xMiden/miden-base/pull/2508)). +- [BREAKING] The stack orientation changed from big-endian to little-endian ([#2508](https://github.com/0xMiden/miden-base/pull/2508)). - Migrated to miden-vm v0.22 and miden-crypto v0.23 ([#2644](https://github.com/0xMiden/protocol/pull/2644)). -- Removed unnecessary `LexicographicWord` wrapper from `StorageMapDelta` and `LinkMap` operations since `Word` now implements the same ordering ([#2662](https://github.com/0xMiden/protocol/pull/2662)). -- [BREAKING] Renamed `NoteLocation::node_index_in_block` to `NoteLocation::block_note_tree_index` ([#2663](https://github.com/0xMiden/protocol/pull/2663)). -- [BREAKING] Made fields of `TransactionOutputs` private ([#2663](https://github.com/0xMiden/protocol/pull/2663)). -- [BREAKING] Renamed `NoteHeader::commitment` to `NoteHeader::to_commitment` ([#2663](https://github.com/0xMiden/protocol/pull/2663)). -- [BREAKING] Changed `TransactionId` to include fee asset in hash computation, making it commit to entire `TransactionHeader` contents. -- Explicitly use `get_native_account_active_storage_slots_ptr` in `account::set_item` and `account::set_map_item`. -- [BREAKING] Introduced `PrivateNoteHeader` for output notes and removed `RawOutputNote::Header` variant ([#2569](https://github.com/0xMiden/protocol/pull/2569)). -- [BREAKING] Changed `asset::create_fungible_asset` and `faucet::create_fungible_asset` signature to take `enable_callbacks` flag ([#2571](https://github.com/0xMiden/protocol/pull/2571)). -- Added Ownable2Step as an Account Component ([#2572](https://github.com/0xMiden/protocol/pull/2572)). - -### Fixes - -- Fixed `PartialAccountTree::track_account` rejecting provably-empty leaves in sparse trees by handling `SmtLeaf::Empty` correctly ([#2598](https://github.com/0xMiden/protocol/pull/2598)). - -## 0.13.3 (2026-01-27) - -- Fixed `CLAIM` note creation to use `NetworkAccountTarget` attachment ([#2352](https://github.com/0xMiden/miden-base/pull/2352)). -- Added standards for working with `NetworkAccountTarget` attachments ([#2338](https://github.com/0xMiden/miden-base/pull/2338)). -- Fixed `PartialBlockchain::add_block()` not adding block headers to the `blocks` map when `track=true`, which caused `prune_to()` to never untrack old blocks, leading to unbounded memory growth ([#2353](https://github.com/0xMiden/miden-base/pull/2353)). - -## 0.13.2 (2026-01-21) - -- Make transaction executor respect debug mode settings ([#2327](https://github.com/0xMiden/miden-base/pull/2327)). - -## 0.13.1 (2026-01-20) - -- Make `NetworkAccountTargetError` public ([#2319](https://github.com/0xMiden/miden-base/pull/2319)). - -## 0.13.0 (2026-01-16) - -### Features - -- [BREAKING] Refactored storage slots to be accessed by names instead of indices ([#1987](https://github.com/0xMiden/miden-base/pull/1987), [#2025](https://github.com/0xMiden/miden-base/pull/2025), [#2149](https://github.com/0xMiden/miden-base/pull/2149), [#2150](https://github.com/0xMiden/miden-base/pull/2150), [#2153](https://github.com/0xMiden/miden-base/pull/2153), [#2154](https://github.com/0xMiden/miden-base/pull/2154), [#2160](https://github.com/0xMiden/miden-base/pull/2160), [#2161](https://github.com/0xMiden/miden-base/pull/2161), [#2170](https://github.com/0xMiden/miden-base/pull/2170)). -- [BREAKING] Allowed account components to share identical account code procedures ([#2164](https://github.com/0xMiden/miden-base/pull/2164)). -- Add `AccountId::parse()` helper function to parse both hex and bech32 formats ([#2223](https://github.com/0xMiden/miden-base/pull/2223)). -- Add Keccak-based MMR frontier structure to the Agglayer library ([#2245](https://github.com/0xMiden/miden-base/pull/2245)). -- Add `read_foreign_account_inputs()`, `read_vault_asset_witnesses()`, and `read_storage_map_witness()` for `TransactionInputs` ([#2246](https://github.com/0xMiden/miden-base/pull/2246)). -- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260), [#2268](https://github.com/0xMiden/miden-base/pull/2268), [#2279](https://github.com/0xMiden/miden-base/pull/2279)). -- Introduce standard `NetworkAccountTarget` attachment for use in network transactions which replaces `NoteTag::NetworkAccount` ([#2257](https://github.com/0xMiden/miden-base/pull/2257)). -- Add a foundry test suite for verifying AggLayer contracts compatibility ([#2312](https://github.com/0xMiden/miden-base/pull/2312)). -- Added `AccountSchemaCommitment` component to expose account storage schema commitments ([#2253](https://github.com/0xMiden/miden-base/pull/2253)). -- Added an `AccountBuilder` extension trait to help build the schema commitment; added `AccountComponentMetadata` to `AccountComponent` ([#2269](https://github.com/0xMiden/miden-base/pull/2269)). -- Added `miden::standards::access::ownable` standard module for component ownership management, and integrated it into the `network_fungible` faucet (including new tests). ([#2228](https://github.com/0xMiden/miden-base/pull/2228)). -- [BREAKING] Add `leaf_value` to `CLAIM` note inputs ([#2290](https://github.com/0xMiden/miden-base/pull/2290)). - -### Changes - -- Added proc-macro `WordWrapper` to ease implementation of `Word`-wrapping types ([#2071](https://github.com/0xMiden/miden-base/pull/2108)). -- [BREAKING] Added `BlockBody` and `BlockProof` structs in preparation for validator signatures and deferred block proving ([#2012](https://github.com/0xMiden/miden-base/pull/2012)). -- [BREAKING] Renamed `TransactionEvent` into `TransactionEventId` and split event handling into data extraction and handling logic ([#2071](https://github.com/0xMiden/miden-base/pull/2071)). -- Split tx progress events out into a separate enum ([#2103](https://github.com/0xMiden/miden-base/pull/2103)). -- Added `note::get_network_account_tag` procedure ([#2120](https://github.com/0xMiden/miden-base/pull/2120)). -- [BREAKING] Updated MINT note to support both private and public output note creation ([#2123](https://github.com/0xMiden/miden-base/pull/2123)). -- [BREAKING] Removed `AccountComponentTemplate` in favor of instantiating components via `AccountComponent::from_package` ([#2127](https://github.com/0xMiden/miden-base/pull/2127)). -- [BREAKING] Added public key to, remove proof commitment from, `BlockHeader`, and add signing functionality through `BlockSigner` trait ([#2128](https://github.com/0xMiden/miden-base/pull/2128)). -- [BREAKING] Added fee to `TransactionHeader` ([#2131](https://github.com/0xMiden/miden-base/pull/2131)). -- Created `NullifierLeafValue` newtype wrapper ([#2136](https://github.com/0xMiden/miden-base/pull/2136)). -- [BREAKING] Increased `MAX_INPUTS_PER_NOTE` from 128 to 1024 ([#2139](https://github.com/0xMiden/miden-base/pull/2139)). -- Added the ability to get full public key from `TransactionAuthenticator` ([#2145](https://github.com/0xMiden/miden-base/pull/2145)). -- Added `TokenSymbol::from_static_str` const function for compile-time token symbol validation ([#2148](https://github.com/0xMiden/miden-base/pull/2148)). -- [BREAKING] Migrated to `miden-vm` v0.20 and `miden-crypto` v0.19 ([#2158](https://github.com/0xMiden/miden-base/pull/2158)). -- [BREAKING] Renamed `AccountProcedureInfo` into `AccountProcedureRoot` and remove storage offset and size ([#2162](https://github.com/0xMiden/miden-base/pull/2162)). -- [BREAKING] Made `AccountProcedureIndexMap` construction infallible ([#2163](https://github.com/0xMiden/miden-base/pull/2163)). -- [BREAKING] Renamed `tracked_procedure_roots_slot` to `trigger_procedure_roots_slot` in ACL auth components for naming consistency ([#2166](https://github.com/0xMiden/miden-base/pull/2166)). -- [BREAKING] Refactored `miden-objects` and `miden-lib` into `miden-protocol` and `miden-standards` ([#2184](https://github.com/0xMiden/miden-base/pull/2184), [#2191](https://github.com/0xMiden/miden-base/pull/2191), [#2197](https://github.com/0xMiden/miden-base/pull/2197), [#2255](https://github.com/0xMiden/miden-base/pull/2255)). -- Added `From<&ExecutedTransaction> for TransactionHeader` implementation ([#2178](https://github.com/0xMiden/miden-base/pull/2178)). -- [BREAKING] Refactored `AccountStorageDelta` to use a new `StorageSlotDelta` type ([#2182](https://github.com/0xMiden/miden-base/pull/2182)). -- [BREAKING] Removed OLD_MAP_ROOT from being returned when calling [`native_account::set_map_item`](crates/miden-lib/asm/miden/native_account.masm) ([#2194](https://github.com/0xMiden/miden-base/pull/2194)). -- [BREAKING] Refactored account component templates into `StorageSchema` ([#2193](https://github.com/0xMiden/miden-base/pull/2193)). -- Added `StorageSchema::commitment()` ([#2244](https://github.com/0xMiden/miden-base/pull/2244)). -- [BREAKING] Refactored account component templates into `AccountStorageSchema` ([#2193](https://github.com/0xMiden/miden-base/pull/2193)). -- [BREAKING] Refactor note tags to be arbitrary `u32` values and drop previous validation ([#2219](https://github.com/0xMiden/miden-base/pull/2219)). -- [BREAKING] Refactored `InitStorageData` to support native types ([#2230](https://github.com/0xMiden/miden-base/pull/2230)). -- Refactored to no longer pad the note inputs on insertion into advice map ([#2232](https://github.com/0xMiden/miden-base/pull/2232)). -- Added `StorageSchema::commitment()` ([#2244](https://github.com/0xMiden/miden-base/pull/2244)). -- [BREAKING] `RpoFalcon512` was renamed to `Falcon512Rpo` everywhere, including procedure and file names ([#2264](https://github.com/0xMiden/miden-base/pull/2264)). -- [BREAKING] Removed top-level error exports from `miden-protocol` crate (the are still accessible under `miden_protocol::errors`). - -## 0.12.4 (2025-11-26) - -- Added the standard library's precompile registry to `TransactionVerifier` ([#2116](https://github.com/0xMiden/miden-base/pull/2116)). - -## 0.12.3 (2025-11-15) - -- Added `ecdsa_k256_keccak::PublicKey` as a valid template type ([#2097](https://github.com/0xMiden/miden-base/pull/2097)). -- [BREAKING] Fix advice inputs in transaction inputs not being propagated through ([#2099](https://github.com/0xMiden/miden-base/pull/2099)). -- Add `S` generic to `NullifierTree` to allow usage with `LargeSmt`s ([#1353](https://github.com/0xMiden/miden-node/issues/1353)). -- [BREAKING] Pre-fetch note and fee asset witnesses before transaction execution ([#2113](https://github.com/0xMiden/miden-base/pull/2113)). - -## 0.12.2 (2025-11-12) - -- Added `create_mint_note` and `create_burn_note` helper functions for creating standardized MINT and BURN notes ([#2061](https://github.com/0xMiden/miden-base/pull/2061)). -- [BREAKING] Fix ECDSA signature preparation in `Signature::to_prepared_signature()` method ([#2074](https://github.com/0xMiden/miden-base/pull/2074)). -- Skip value slot normalization for new account's deltas ([#2075](https://github.com/0xMiden/miden-base/pull/2075)). -- Skip value and map slot normalization for new account's deltas ([#2075](https://github.com/0xMiden/miden-base/pull/2075), [#2077](https://github.com/0xMiden/miden-base/pull/2077)). -- Added `AuthEcdsaK256Keccak` and `AuthEcdsaK256KeccakMultisig` auth components ([#2083](https://github.com/0xMiden/miden-base/pull/2083)). - -## 0.12.1 (2025-11-06) - -- Made `InitStorageData::map_entries()` public ([#2055](https://github.com/0xMiden/miden-base/pull/2055)). -- Enabled handling of empty maps in account component templates ([#2056](https://github.com/0xMiden/miden-base/pull/2056)). -- Changed auth components to increment nonce if it is zero ([#2060](https://github.com/0xMiden/miden-base/pull/2060)). -- Fixed incorrect detection of note inputs length during note creation ([#2066](https://github.com/0xMiden/miden-base/pull/2066)). - -## 0.12.0 (2025-11-05) - -### Features - -- Added `prove_dummy` APIs on `LocalTransactionProver` ([#1674](https://github.com/0xMiden/miden-base/pull/1674)). -- Added `update_signers_and_threshold` procedure to update owner public keys and threshold config in multisig authentication component ([#1707](https://github.com/0xMiden/miden-base/issues/1707)). -- Added `add_signature` helper to simplify loading signatures into advice map ([#1725](https://github.com/0xMiden/miden-base/pull/1725)). -- Added `build_recipient` procedure to `miden::note` module ([#1807](https://github.com/0xMiden/miden-base/pull/1807)). -- Added `prove_dummy` APIs on `LocalBatchProver` and `LocalBlockProver` ([#1811](https://github.com/0xMiden/miden-base/pull/1811)). -- Added `get_native_id` and `get_native_nonce` procedures to the `miden` library ([#1844](https://github.com/0xMiden/miden-base/pull/1844)). -- Enabled lazy loading of assets during transaction execution ([#1848](https://github.com/0xMiden/miden-base/pull/1848)). -- Added lazy loading of the native asset ([#1855](https://github.com/0xMiden/miden-base/pull/1855)). -- [BREAKING] Enabled lazy loading of storage map entries during transaction execution ([#1857](https://github.com/0xMiden/miden-base/pull/1857)). -- [BREAKING] Enabled lazy loading of foreign accounts during transaction execution ([#1873](https://github.com/0xMiden/miden-base/pull/1873)). -- [BREAKING] Move account seed into `PartialAccount` ([#1875](https://github.com/0xMiden/miden-base/pull/1875), [#2003](https://github.com/0xMiden/miden-base/pull/2003)). -- Added `get_initial_item` and `get_map_item_init` procedures to `miden::account` module for accessing initial storage state ([#1883](https://github.com/0xMiden/miden-base/pull/1883)). -- Updated `rpo_falcon512::verify_signatures` to use `account::get_map_item_init` ([#1885](https://github.com/0xMiden/miden-base/issues/1885)). -- [BREAKING] Enabled lazy loading of assets and storage map items for foreign accounts during transaction execution ([#1888](https://github.com/0xMiden/miden-base/pull/1888)). -- [BREAKING] Represent new accounts as account deltas ([#1896](https://github.com/0xMiden/miden-base/pull/1896)). -- Implement `SlotName` for named storage slots ([#1932](https://github.com/0xMiden/miden-base/issues/1932)) -- [BREAKING] Removed `get_falcon_signature` from `miden-tx` crate ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). -- Created a `Signature` wrapper to simplify the preparation of "native" signatures for use in the VM ([#1924](https://github.com/0xMiden/miden-base/pull/1924)). -- Added per-procedure approval thresholds to `AuthRpoFalcon512Multisig` auth component ([#1968](https://github.com/0xMiden/miden-base/pull/1968)). -- Implemented `input_note::get_sender` and `active_note::get_metadata` procedures in `miden` lib ([#1933](https://github.com/0xMiden/miden-base/pull/1933)). -- Added `Address` serialization and deserialization ([#1937](https://github.com/0xMiden/miden-base/issues/1937)). -- Added `StorageMap::{num_entries, num_leaves}` to retrieve the number of entries in a storage map ([#1935](https://github.com/0xMiden/miden-base/pull/1935)). -- Added `AssetVault::{num_assets, num_leaves, inner_nodes}` ([#1939](https://github.com/0xMiden/miden-base/pull/1939)). -- [BREAKING] Enabled computing the transaction ID from the data in a `TransactionHeader` ([#1973](https://github.com/0xMiden/miden-base/pull/1973)). -- Added `account::get_initial_balance` procedure to `miden` lib ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). -- [BREAKING] Changed `Account` to `PartialAccount` conversion to generally track only minimal data ([#1963](https://github.com/0xMiden/miden-base/pull/1963)). -- Added `MastArtifact`, `PackageExport`, `PackageManifest`, `AttributeSet`, `QualifiedProcedureName`, `Section` and `SectionId` to re-export section ([#1984](https://github.com/0xMiden/miden-base/pull/1984) and [#2015](https://github.com/0xMiden/miden-base/pull/2015)). -- [BREAKING] Enable computing the transaction ID from the data in a `TransactionHeader` ([#1973]https://github.com/0xMiden/miden-base/pull/1973). -- [BREAKING] Introduce `VaultKey` newtype wrapper for asset vault keys ([#1978]https://github.com/0xMiden/miden-base/pull/1978). -- [BREAKING] Introduce `AssetVaultKey` newtype wrapper for asset vault keys ([#1978](https://github.com/0xMiden/miden-base/pull/1978), [#2024](https://github.com/0xMiden/miden-base/pull/2024)). -- Added `network_fungible_faucet` and `MINT` & `BURN` notes ([#1925](https://github.com/0xMiden/miden-base/pull/1925)) -- Removed `create_p2id_note` and `create_p2any_note` methods from `MockChainBuilder`, users should use `add_p2id_note` and `add_p2any_note` instead ([#1990](https://github.com/0xMiden/miden-base/issues/1990)). -- [BREAKING] Introduced `AuthScheme` and `PublicKey` enums in `miden-objects::account::auth` module ([#1994](https://github.com/0xMiden/miden-base/pull/1994)). -- [BREAKING] Added `get_note_script()` method to `DataStore` trait to enable lazy loading of note scripts during transaction execution ([#1995](https://github.com/0xMiden/miden-base/pull/1995)). -- Added `AccountTree::apply_mutations_with_reversions` ([#2002](https://github.com/0xMiden/miden-base/pull/2002)). -- [BREAKING] Change `AccountTree` to be generic over `trait AccountTreeBackend` implementations ([#2006](https://github.com/0xMiden/miden-base/pull/2006)). -- Added `Display` trait for `AddressInterface` ([#2016](https://github.com/0xMiden/miden-base/pull/2016)). -- Added `has_procedure` procedure to the `miden::account` module ([#2017](https://github.com/0xMiden/miden-base/pull/2017)). -- Re-add bech32 encoding for `AccountId` ([#2018](https://github.com/0xMiden/miden-base/pull/2018)). -- [BREAKING] Separate account APIs in `miden::account` into `active_account` and `native_account` ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). -- [BREAKING] Remove `miden::account::get_native_nonce` procedure ([#2026](https://github.com/0xMiden/miden-base/pull/2026)). -- [BREAKING] Refactor `Address` to make routing parameters optional ([#2032](https://github.com/0xMiden/miden-base/pull/2032), [#2047](https://github.com/0xMiden/miden-base/pull/2047)). -- [BREAKING] Refactor `PartialVault`, `PartialStorageMap`, `PartialAccountTree` and `PartialNullifierTree` to allow construction from a root ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). -- Added duplicate approver validation to `AuthRpoFalcon512MultisigConfig` ([#2046](https://github.com/0xMiden/miden-base/issues/2046)). -- Added `encryption_key` to `RoutingParameters` ([#2050](https://github.com/0xMiden/miden-base/pull/2050)). -- [BREAKING] Added `EcdsaK256Keccak` variant to auth enums ([#2052](https://github.com/0xMiden/miden-base/pull/2052)). -- Implemented storage map templates, which can be initialized through key/value lists provided via `InitStorageData` TOML ([#2053](https://github.com/0xMiden/miden-base/pull/2053)). - -### Changes - -- [BREAKING] Incremented MSRV to 1.90. -- [BREAKING] Migrated to `miden-vm` v0.18 and `miden-crypto` v0.17 ([#1832](https://github.com/0xMiden/miden-base/pull/1832)). -- [BREAKING] Removed `MockChain::add_pending_p2id_note` in favor of using `MockChainBuilder` ([#1842](https://github.com/0xMiden/miden-base/pull/#1842)). -- [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). -- [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). -- [BREAKING] Removed versioning of the transaction kernel, leaving only one latest version ([#1793](https://github.com/0xMiden/miden-base/pull/1793)). -- Added `AccountComponent::from_package()` method to create components from `miden-mast-package::Package` ([#1802](https://github.com/0xMiden/miden-base/pull/1802)). -- [BREAKING] Removed some of the `note` kernel procedures and use `input_note` procedures instead ([#1834](https://github.com/0xMiden/miden-base/pull/1834)). -- [BREAKING] Replaced `Account` with `PartialAccount` in `TransactionInputs` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). -- [BREAKING] Renamed `Account::init_commitment` to `Account::initial_commitment` ([#1840](https://github.com/0xMiden/miden-base/pull/1840)). -- [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). -- [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). -- Removed `ProvenTransactionExt`([#1867](https://github.com/0xMiden/miden-base/pull/1867)). -- [BREAKING] Renamed the `is_onchain` method to `has_public_state` for `AccountId`, `AccountIdPrefix`, `Account`, `AccountInterface` and `AccountStorageMode` ([#1846](https://github.com/0xMiden/miden-base/pull/1846)). -- [BREAKING] Moved `miden::asset::{create_fungible_asset, create_non_fungible_asset}` procedures to `miden::faucet` ([#1850](https://github.com/0xMiden/miden-base/pull/1850)). -- [BREAKING] Moved `NetworkId` from account ID to address module ([#1851](https://github.com/0xMiden/miden-base/pull/1851)). -- [BREAKING] Moved `TransactionKernelError` to miden-tx ([#1859](https://github.com/0xMiden/miden-base/pull/1859)). -- [BREAKING] Changed `PartialStorageMap` to track the correct set of key+value pairings ([#1878](https://github.com/0xMiden/miden-base/pull/1878), [#1921](https://github.com/0xMiden/miden-base/pull/1921)). -- Change terminology of "current note" to "active note" ([#1863](https://github.com/0xMiden/miden-base/issues/1863)). -- [BREAKING] Moved and rename `miden::tx::{add_asset_to_note, create_note}` procedures to `miden::output_note::{add_asset, create}` ([#1874](https://github.com/0xMiden/miden-base/pull/1874)). -- Merge `bench-prover` into `bench-tx` crate ([#1894](https://github.com/0xMiden/miden-base/pull/1894)). -- Replace `eqw` usages with `exec.word::test_eq` and `exec.word::eq`, remove `is_key_greater` and `is_key_less` from `link_map` module ([#1897](https://github.com/0xMiden/miden-base/pull/1897)). -- [BREAKING] Make AssetVault and PartialVault APIs more type safe ([#1916](https://github.com/0xMiden/miden-base/pull/1916)). -- [BREAKING] Remove `MockChain::add_pending_note` to simplify mock chain internals ([#1903](https://github.com/0xMiden/miden-base/pull/1903)). -- [BREAKING] Moved active note procedures from `miden::note` to `miden::active_note` module ([#1901](https://github.com/0xMiden/miden-base/pull/1901)). -- [BREAKING] Removed account_seed from AccountFile ([#1917](https://github.com/0xMiden/miden-base/pull/1917)). -- [BREAKING] Renamed `TransactionInputs` to `TransactionExecutionInputs` and make a new `TransactionInputs` struct which does not contain `InputNotes` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). -- [BREAKING] Refactored `TransactionInputs` and remove `TransactionWitness` ([#1934](https://github.com/0xMiden/miden-base/pull/1934)). -- Simplify `MockChain` internals and rework its documentation ([#1942](https://github.com/0xMiden/miden-base/pull/1942)). -- [BREAKING] Changed the signature of TransactionAuthenticator to return the native signature ([#1945](https://github.com/0xMiden/miden-base/pull/1945)). -- [BREAKING] Renamed `MockChainBuilder::add_note` to `add_output_note` ([#1946](https://github.com/0xMiden/miden-base/pull/1946)). -- Dynamically lookup all masm `EventId`s from source ([#1954](https://github.com/0xMiden/miden-base/pull/1954)). -- [BREAKING] Return `ExecutionOutput` from `TransactionContext::execute_code` ([#1955](https://github.com/0xMiden/miden-base/pull/1955)). -- [BREAKING] Renamed `get_item_init` and `get_map_item_init` to `get_initial_item` and `get_initial_map_item` respectively ([#1959](https://github.com/0xMiden/miden-base/pull/1959)). -- Update the type signature syntax in the `account_components` module ([#1971](https://github.com/0xMiden/miden-base/pull/1971)). -- [BREAKING] Assert nonce is non-zero after the auth procedure ([#1982](https://github.com/0xMiden/miden-base/pull/1982)). -- [BREAKING] Removed `Rng` from `BasicAuthenticator` ([#1994](https://github.com/0xMiden/miden-base/pull/1994)). -- [BREAKING] Changed the outputs of the `output_note::add_asset` procedure: now the values that are the same as the passed parameters are dropped ([#2031](https://github.com/0xMiden/miden-base/pull/2031)). -- [BREAKING] Upgraded VM to 0.19 ([#2042](https://github.com/0xMiden/miden-base/pull/2042)). - -## 0.11.5 (2025-10-02) - -- Add new `can_consume` method to the `NoteConsumptionChecker` ([#1928](https://github.com/0xMiden/miden-base/pull/1928)). - -## 0.11.4 (2025-09-17) - -- Updated `miden-vm` dependencies to `0.17.2` patch version. ([#1905](https://github.com/0xMiden/miden-base/pull/1905)) - -## 0.11.3 (2025-09-15) - -- Added Serialize and Deserialize Traits on `SigningInputs` ([#1858](https://github.com/0xMiden/miden-base/pull/1858)). - -## 0.11.2 (2025-09-08) - -- Fixed foreign account inputs not being loaded in `LocalTransactionProver` ([#1866](https://github.com/0xMiden/miden-base/pull/#1866)). - -## 0.11.1 (2025-08-28) - -- Added `AddressInterface::Unspecified` to represent default addresses ([#1801](https://github.com/0xMiden/miden-base/pull/#1801)). - -## 0.11.0 (2025-08-26) - -### Features - -- Added arguments to the auth procedure ([#1501](https://github.com/0xMiden/miden-base/pull/1501)). -- [BREAKING] Refactored `SWAP` note & added option to select the visibility of the associated payback note ([#1539](https://github.com/0xMiden/miden-base/pull/1539)). -- Added multi-signature authentication component as standard authentication component ([#1599](https://github.com/0xMiden/miden-base/issues/1599)). -- Added `account_compute_delta_commitment`, `input_note_get_assets_info`, `tx_get_num_input_notes`, and `tx_get_num_output_notes` procedures to the transaction kernel ([#1609](https://github.com/0xMiden/miden-base/pull/1609)). -- [BREAKING] Refactor `TransactionAuthenticator` to support arbitrary data signing ([#1616](https://github.com/0xMiden/miden-base/pull/1616)). -- Implemented new `from_unauthenticated_notes` constructor for `InputNotes` ([#1629](https://github.com/0xMiden/miden-base/pull/1629)). -- Added `output_note_get_assets_info` procedure to the transaction kernel ([#1638](https://github.com/0xMiden/miden-base/pull/1638)). -- Pass the full `TransactionSummary` to `TransactionAuthenticator` ([#1618](https://github.com/0xMiden/miden-base/pull/1618)). -- Added `PartialBlockchain::num_tracked_blocks()` ([#1643](https://github.com/0xMiden/miden-base/pull/1643)). -- Removed `TransactionScript::compile` & `NoteScript::compile` methods in favor of `ScriptBuilder` ([#1665](https://github.com/0xMiden/miden-base/pull/1665)). -- Added `get_initial_code_commitment`, `get_initial_storage_commitment` and `get_initial_vault_root` procedures to `miden::account` module ([#1667](https://github.com/0xMiden/miden-base/pull/1667)). -- Added `input_note_get_recipient`, `output_note_get_recipient`, `input_note_get_metadata`, `output_note_get_metadata` procedures to the transaction kernel ([#1648](https://github.com/0xMiden/miden-base/pull/1648)). -- Added `input_notes::get_assets` and `output_notes::get_assets` procedures to `miden` library ([#1648](https://github.com/0xMiden/miden-base/pull/1648)). -- Added issuance accessor for fungible faucet accounts. ([#1660](https://github.com/0xMiden/miden-base/pull/1660)). -- Added multi-signature authentication component as standard authentication component ([#1599](https://github.com/0xMiden/miden-base/issues/1599)). -- Added `FeeParameters` to `BlockHeader` and automatically compute and remove fees from account in the transaction kernel epilogue ([#1652](https://github.com/0xMiden/miden-base/pull/1652), [#1654](https://github.com/0xMiden/miden-base/pull/1654), [#1659](https://github.com/0xMiden/miden-base/pull/1659), [#1664](https://github.com/0xMiden/miden-base/pull/1664), [#1775](https://github.com/0xMiden/miden-base/pull/1775)). -- Added `Address` type to represent account-id based addresses ([#1713](https://github.com/0xMiden/miden-base/pull/1713), [#1750](https://github.com/0xMiden/miden-base/pull/1750)). -- [BREAKING] Consolidated to a single async interface and drop `#[maybe_async]` usage ([#1666](https://github.com/0xMiden/miden-base/pull/#1666)). -- [BREAKING] Made transaction execution and transaction authentication asynchronous ([#1699](https://github.com/0xMiden/miden-base/pull/1699)). -- [BREAKING] Return dedicated insufficient fee error from transaction host if account balance is too low ([#1744](https://github.com/0xMiden/miden-base/pull/#1744)). -- Added `asset_vault::peek_balance` ([#1745](https://github.com/0xMiden/miden-base/pull/1745)). -- Added `get_auth_scheme` method to `AccountComponentInterface` and `AccountInterface` for better authentication scheme extraction ([#1759](https://github.com/0xMiden/miden-base/pull/1759)). -- Added `AddressInterface` type to represent the interface of the account to which an `Address` points ([#1761](https://github.com/0xMiden/miden-base/pull/#1761)). -- Document `miden` library procedures and the context from which they can be called ([#1799](https://github.com/0xMiden/miden-base/pull/#1799)). -- Add `Address` type to represent account-id based addresses ([#1713](https://github.com/0xMiden/miden-base/pull/1713)). -- Document `Address` in Miden book ([#1792](https://github.com/0xMiden/miden-base/pull/1792)). -- Add `asset_vault::peek_balance` ([#1745](https://github.com/0xMiden/miden-base/pull/1745)). -- Add `get_auth_scheme` method to `AccountComponentInterface` and `AccountInterface` for better authentication scheme extraction ([#1759](https://github.com/0xMiden/miden-base/pull/1759)). -- Add `CustomNetworkId` in `NetworkID` ([#1787](https://github.com/0xMiden/miden-base/pull/1787)). - -### Changes - -- [BREAKING] Incremented MSRV to 1.88. -- Refactored account documentation into multiple sections ([#1523](https://github.com/0xMiden/miden-base/pull/1523)). -- Implemented `WellKnownComponents` enum ([#1532](https://github.com/0xMiden/miden-base/pull/1532)). -- [BREAKING] Remove pending account APIs on `MockChain` and introduce `MockChainBuilder` to simplify mock chain creation ([#1557](https://github.com/0xMiden/miden-base/pull/1557)). -- Made `ExecutedTransaction` implement `Send` for easier consumption ([#1560](https://github.com/0xMiden/miden-base/pull/1560)). -- [BREAKING] `Digest` was removed in favor of `Word` ([#1564](https://github.com/0xMiden/miden-base/pull/1564)). -- [BREAKING] Upgraded Miden VM to `0.16`, `miden-crypto` to `0.15` and `winterfell` crates to `0.13` ([#1564](https://github.com/0xMiden/miden-base/pull/1564), [#1594](https://github.com/0xMiden/miden-base/pull/1594)). -- [BREAKING] Renamed `{NoteInclusionProof, AccountWitness}::inner_nodes` to `authenticated_nodes` ([#1564](https://github.com/0xMiden/miden-base/pull/1564)). -- [BREAKING] Renamed `{TransactionId, NoteId, Nullifier}::inner` -> `as_word` ([#1571](https://github.com/0xMiden/miden-base/pull/1571)). -- Replaced `MerklePath` with `SparseMerklePath` in `NoteInclusionProof` ([#1572](https://github.com/0xMiden/miden-base/pull/1572)) . -- [BREAKING] Renamed authentication components to include "auth" prefix for clarity ([#1575](https://github.com/0xMiden/miden-base/issues/1575)). -- [BREAKING] Split `TransactionHost` into `TransactionProverHost` and `TransactionExecutorHost` ([#1581](https://github.com/0xMiden/miden-base/pull/1581)). -- Added `TransactionEvent::Unauthorized` to enable aborting the transaction execution to get its transaction summary for signing purposes ([#1596](https://github.com/0xMiden/miden-base/pull/1596), [#1634](https://github.com/0xMiden/miden-base/pull/1634), [#1651](https://github.com/0xMiden/miden-base/pull/1651)). -- [BREAKING] Implemented `SequentialCommit` for `AccountDelta` and renamed `AccountDelta::commitment()` to `AccountDelta::to_commitment()` ([#1603](https://github.com/0xMiden/miden-base/pull/1603)). -- Added robustness check to `create_swap_note`: error if `requested_asset` != `offered_asset` ([#1604](https://github.com/0xMiden/miden-base/pull/1604)). -- [BREAKING] Changed `account::incr_nonce` to always increment the nonce by one, disallow incrementing more than once and return the new nonce after incrementing ([#1608](https://github.com/0xMiden/miden-base/pull/1608), [#1633](https://github.com/0xMiden/miden-base/pull/1633)). -- Added `AccountTree::contains_account_id_prefix()` and `AccountTree::id_prefix_to_smt_key()` ([#1610](https://github.com/0xMiden/miden-base/pull/1610)). -- Added functions for pruning `PartialBlockchain` (#[1619](https://github.com/0xMiden/miden-base/pull/1619)). -- [BREAKING] Disallowed calling the auth procedure explicitly (from outside the epilogue) ([#1622](https://github.com/0xMiden/miden-base/pull/1622)). -- [BREAKING] Included account delta commitment in signing message for the `RpoFalcon512` family of account components ([#1624](https://github.com/0xMiden/miden-base/pull/1624)). -- [BREAKING] Renamed `TransactionEvent::FalconSigToStack` to `TransactionEvent::AuthRequest` ([#1626](https://github.com/0xMiden/miden-base/pull/1626)). -- [BREAKING] Made the naming of the transaction script arguments consistent ([#1632](https://github.com/0xMiden/miden-base/pull/1632)). -- [BREAKING] Moved `TransactionProverHost` and `TransactionExecutorHost` from dynamic dispatch to generics ([#1037](https://github.com/0xMiden/miden-node/issues/1037)) -- [BREAKING] Changed `PartialStorage` and `PartialVault` to use `PartialSmt` instead of separate merkle proofs ([#1590](https://github.com/0xMiden/miden-base/pull/1590)). -- [BREAKING] Moved transaction inputs insertion out of transaction hosts ([#1639](https://github.com/0xMiden/miden-node/issues/1639)) -- Implemented serialization for `MockChain` ([#1642](https://github.com/0xMiden/miden-base/pull/1642)). -- [BREAKING] Reduced `FungibleAsset::MAX_AMOUNT` by a small fraction which allows using felt-based arithmetic in the fungible asset account delta ([#1681](https://github.com/0xMiden/miden-base/pull/1681)). -- Avoid modifying an asset vault when adding a fungible asset with amount zero and the asset does not already exist ([#1668](https://github.com/0xMiden/miden-base/pull/1668)). -- [BREAKING] Updated `NoteConsumptionChecker::check_notes_consumability` and `TransactionExecutor::try_execute_notes` to return `NoteConsumptionInfo` containing lists of `Note` rather than `NoteId` ([#1680](https://github.com/0xMiden/miden-base/pull/1680)). -- Refactored epilogue to run as much code as possible before fees are computed ([#1698](https://github.com/0xMiden/miden-base/pull/1698)). -- Refactored epilogue to run as much code as possible before fees are computed ([#1698](https://github.com/0xMiden/miden-base/pull/1698), [#1705](https://github.com/0xMiden/miden-base/pull/1705)). -- [BREAKING] Removed note script utils and rename `note::add_note_assets_to_account` to `note::add_assets_to_account` ([#1694](https://github.com/0xMiden/miden-base/pull/1694)). -- Refactor `contracts::auth::basic` into a reusable library procedure `auth::rpo_falcon512` ([#1712](https://github.com/0xMiden/miden-base/pull/1712)). -- [BREAKING] Refactored `FungibleAsset::sub` to be more similar to `FungibleAsset::add` ([#1720](https://github.com/0xMiden/miden-base/pull/1720)). -- Update `NoteConsumptionChecker::check_notes_consumability` to use iterative elimination strategy to find a set of executable notes ([#1721](https://github.com/0xMiden/miden-base/pull/1721)). -- [BREAKING] Moved `IncrNonceAuthComponent`, `ConditionalAuthComponent` and `AccountMockComponent` to `miden-lib` ([#1722](https://github.com/0xMiden/miden-base/pull/1722)). -- [BREAKING] Split `AccountCode::mock_library` into an account and faucet library ([#1732](https://github.com/0xMiden/miden-base/pull/1732), [#1733](https://github.com/0xMiden/miden-base/pull/1733)). -- [BREAKING] Refactored `AccountError::AssumptionViolated` into `AccountError::Other` ([#1743](https://github.com/0xMiden/miden-base/pull/1743)). -- [BREAKING] Removed `PartialVault::{new, add}` to guarantee the vault tracks valid assets ([#1747](https://github.com/0xMiden/miden-base/pull/1747)). -- [BREAKING] Changed owner of `Arc` to `Felt` ([#1492](https://github.com/0xMiden/miden-base/pull/1492)). -- Normalized account deltas to avoid including no-op updates ([#1496](https://github.com/0xMiden/miden-base/pull/1496)). -- Added `Note::is_network_note()` accessor ([#1485](https://github.com/0xMiden/miden-base/pull/1485)). -- [BREAKING] Refactored account authentication to require a procedure containing `auth__` in its name ([#1480](https://github.com/0xMiden/miden-base/pull/1480)). -- [BREAKING] Updated handling of the shared modules ([#1490](https://github.com/0xMiden/miden-base/pull/1490)). -- [BREAKING] Refactored transaction to output `ACCOUNT_UPDATE_COMMITMENT` ([#1500](https://github.com/0xMiden/miden-base/pull/1500)). -- Added a new constructor for `TransactionExecutor` that accepts `ExecutionOptions` ([#1502](https://github.com/0xMiden/miden-base/pull/1502)). -- [BREAKING] Introduced errors in `MockChain` API ([#1508](https://github.com/0xMiden/miden-base/pull/1508)). -- [BREAKING] `TransactionAdviceInputs` cannot return `Err` anymore ([#1517](https://github.com/0xMiden/miden-base/pull/1517)). -- Implemented serialization for `LexicographicWord` ([#1524](https://github.com/0xMiden/miden-base/pull/1524)). -- Made `Account:increment_nonce()` method public ([#1533](https://github.com/0xMiden/miden-base/pull/1533)). -- Defined the commitment to an empty account delta as `EMPTY_WORD` ([#1528](https://github.com/0xMiden/miden-base/pull/1528)). -- [BREAKING] Renamed `account_get_current_commitment` to `account_compute_current_commitment` and include the latest storage commitment in the returned commitment ([#1529](https://github.com/0xMiden/miden-base/pull/1529)). -- [BREAKING] Remove `create_note` from `BasicWallet`, expose it and `add_asset_to_note` in `miden::tx` ([#1525](https://github.com/0xMiden/miden-base/pull/1525)). -- Add a new auth component `RpoFalcon512Acl` ([#1531](https://github.com/0xMiden/miden-base/pull/1531)). -- [BREAKING] Change `BasicFungibleFaucet` to use `RpoFalcon512Acl` for authentication ([#1531](https://github.com/0xMiden/miden-base/pull/1531)). -- Introduce `MockChain` methods for executing at an older block (#1541). -- [BREAKING] Change authentication component procedure name prefix from `auth__*` to `auth_*` ([#1861](https://github.com/0xMiden/miden-base/issues/1861)). - -### Fixes - -- [BREAKING] Forbid the execution of the empty transactions ([#1459](https://github.com/0xMiden/miden-base/pull/1459)). - -## 0.9.5 (2025-06-20) - `miden-lib` crate only - -- Added `symbol()`, `decimals()`, and `max_supply()` accessors to the `TokenSymbol` struct. - -## 0.9.4 (2025-06-12) - -- Refactor proving service client errors ([#1448](https://github.com/0xMiden/miden-base/pull/1448)) - -## 0.9.3 (2025-06-12) - -- Add TLS support to `miden-proving-service-client` ([#1447](https://github.com/0xMiden/miden-base/pull/1447)) - -## 0.9.2 (2025-06-10) - -- Refreshed Cargo.lock file. - -## 0.9.1 (2025-05-30) - -### Fixes - -- Expose types used in public APIs ([#1385](https://github.com/0xMiden/miden-base/pull/1385)). -- Version check always fails in proxy ([#1407](https://github.com/0xMiden/miden-base/pull/1407)). - -## 0.9.0 (2025-05-20) - -### Features - -- Added pretty print for `AccountCode` ([#1273](https://github.com/0xMiden/miden-base/pull/1273)). -- Add iterators over concrete asset types in `NoteAssets` ([#1346](https://github.com/0xMiden/miden-base/pull/1346)). -- Add the ability to create `BasicFungibleFaucet` from `Account` ([#1376](https://github.com/0xMiden/miden-base/pull/1376)). - -### Fixes - -- [BREAKING] Hash keys in storage maps before insertion into the SMT ([#1250](https://github.com/0xMiden/miden-base/pull/1250)). -- Fix error when creating accounts with empty storage ([#1307](https://github.com/0xMiden/miden-base/pull/1307)). -- [BREAKING] Move the number of note inputs to the separate memory address ([#1327](https://github.com/0xMiden/miden-base/pull/1327)). -- [BREAKING] Change Token Symbol encoding ([#1334](https://github.com/0xMiden/miden-base/pull/1334)). - -### Changes - -- [BREAKING] Refactored how foreign account inputs are passed to `TransactionExecutor` ([#1229](https://github.com/0xMiden/miden-base/pull/1229)). -- [BREAKING] Add `TransactionHeader` and include it in batches and blocks ([#1247](https://github.com/0xMiden/miden-base/pull/1247)). -- Add `AccountTree` and `PartialAccountTree` wrappers and enforce ID prefix uniqueness ([#1254](https://github.com/0xMiden/miden-base/pull/1254), [#1301](https://github.com/0xMiden/miden-base/pull/1301)). -- Added getter for proof security level in `ProvenBatch` and `ProvenBlock` ([#1259](https://github.com/0xMiden/miden-base/pull/1259)). -- [BREAKING] Replaced the `ProvenBatch::new_unchecked` with the `ProvenBatch::new` method to initialize the struct with validations ([#1260](https://github.com/0xMiden/miden-base/pull/1260)). -- [BREAKING] Add `AccountStorageMode::Network` for network accounts ([#1275](https://github.com/0xMiden/miden-base/pull/1275), [#1349](https://github.com/0xMiden/miden-base/pull/1349)). -- Added support for environment variables to set up the `miden-proving-service` worker ([#1281](https://github.com/0xMiden/miden-base/pull/1281)). -- Added field identifier structs for component metadata ([#1292](https://github.com/0xMiden/miden-base/pull/1292)). -- Move `NullifierTree` and `BlockChain` from node to base ([#1304](https://github.com/0xMiden/miden-base/pull/1304)). -- Rename `ChainMmr` to `PartialBlockchain` ([#1305](https://github.com/0xMiden/miden-base/pull/1305)). -- Add safe `PartialBlockchain` constructor ([#1308](https://github.com/0xMiden/miden-base/pull/1308)). -- [BREAKING] Move `MockChain` and `TransactionContext` to new `miden-testing` crate ([#1309](https://github.com/0xMiden/miden-base/pull/1309)). -- [BREAKING] Add support for private notes in `MockChain` ([#1310](https://github.com/0xMiden/miden-base/pull/1310)). -- Generalized account-related inputs to the transaction kernel ([#1311](https://github.com/0xMiden/miden-base/pull/1311)). -- [BREAKING] Refactor `MockChain` to use batch and block provers ([#1315](https://github.com/0xMiden/miden-base/pull/1315)). -- [BREAKING] Upgrade VM to 0.14 and refactor transaction kernel error extraction ([#1353](https://github.com/0xMiden/miden-base/pull/1353)). -- [BREAKING] Update MSRV to 1.87. - -## 0.8.3 (2025-04-22) - `miden-proving-service` crate only - -### Fixes - -- Version check always fails ([#1300](https://github.com/0xMiden/miden-base/pull/1300)). - -## 0.8.2 (2025-04-18) - `miden-proving-service` crate only - -### Changes - -- Added a retry strategy for worker's health check ([#1255](https://github.com/0xMiden/miden-base/pull/1255)). -- Added a status endpoint for the `miden-proving-service` worker and proxy ([#1255](https://github.com/0xMiden/miden-base/pull/1255)). - -## 0.8.1 (2025-03-26) - `miden-objects` and `miden-tx` crates only. - -### Changes - -- [BREAKING] Changed `TransactionArgs` API to accept `AsRef` for extending the advice map in relation to output notes ([#1251](https://github.com/0xMiden/miden-base/pull/1251)). - -## 0.8.0 (2025-03-21) - -### Features - -- Added an endpoint to the `miden-proving-service` to update the workers ([#1107](https://github.com/0xMiden/miden-base/pull/1107)). -- [BREAKING] Added the `get_block_timestamp` procedure to the `miden` library ([#1138](https://github.com/0xMiden/miden-base/pull/1138)). -- Implemented `AccountInterface` structure ([#1171](https://github.com/0xMiden/miden-base/pull/1171)). -- Implement user-facing bech32 encoding for `AccountId`s ([#1185](https://github.com/0xMiden/miden-base/pull/1185)). -- Implemented `execute_tx_view_script` procedure for the `TransactionExecutor` ([#1197](https://github.com/0xMiden/miden-base/pull/1197)). -- Enabled nested FPI calls ([#1227](https://github.com/0xMiden/miden-base/pull/1227)). -- Implement `check_notes_consumability` procedure for the `TransactionExecutor` ([#1269](https://github.com/0xMiden/miden-base/pull/1269)). - -### Changes - -- [BREAKING] Moved `generated` module from `miden-proving-service-client` crate to `tx_prover::generated` hierarchy ([#1102](https://github.com/0xMiden/miden-base/pull/1102)). -- Renamed the protobuf file of the transaction prover to `tx_prover.proto` ([#1110](https://github.com/0xMiden/miden-base/pull/1110)). -- [BREAKING] Renamed `AccountData` to `AccountFile` ([#1116](https://github.com/0xMiden/miden-base/pull/1116)). -- Implement transaction batch prover in Rust ([#1112](https://github.com/0xMiden/miden-base/pull/1112)). -- Added the `is_non_fungible_asset_issued` procedure to the `miden` library ([#1125](https://github.com/0xMiden/miden-base/pull/1125)). -- [BREAKING] Refactored config file for `miden-proving-service` to be based on environment variables ([#1120](https://github.com/0xMiden/miden-base/pull/1120)). -- Added block number as a public input to the transaction kernel. Updated prologue logic to validate the global input block number is consistent with the commitment block number ([#1126](https://github.com/0xMiden/miden-base/pull/1126)). -- Made NoteFile and AccountFile more consistent ([#1133](https://github.com/0xMiden/miden-base/pull/1133)). -- [BREAKING] Implement most block constraints in `ProposedBlock` ([#1123](https://github.com/0xMiden/miden-base/pull/1123), [#1141](https://github.com/0xMiden/miden-base/pull/1141)). -- Added serialization for `ProposedBatch`, `BatchId`, `BatchNoteTree` and `ProvenBatch` ([#1140](https://github.com/0xMiden/miden-base/pull/1140)). -- Added `prefix` to `Nullifier` ([#1153](https://github.com/0xMiden/miden-base/pull/1153)). -- [BREAKING] Implemented a `RemoteBatchProver`. `miden-proving-service` workers can prove batches ([#1142](https://github.com/0xMiden/miden-base/pull/1142)). -- [BREAKING] Implement `LocalBlockProver` and rename `Block` to `ProvenBlock` ([#1152](https://github.com/0xMiden/miden-base/pull/1152), [#1168](https://github.com/0xMiden/miden-base/pull/1168), [#1172](https://github.com/0xMiden/miden-base/pull/1172)). -- [BREAKING] Added native types to `AccountComponentTemplate` ([#1124](https://github.com/0xMiden/miden-base/pull/1124)). -- Implemented `RemoteBlockProver`. `miden-proving-service` workers can prove blocks ([#1169](https://github.com/0xMiden/miden-base/pull/1169)). -- Used `Smt::with_entries` to error on duplicates in `StorageMap::with_entries` ([#1167](https://github.com/0xMiden/miden-base/pull/1167)). -- [BREAKING] Added `InitStorageData::from_toml()`, improved storage entry validations in `AccountComponentMetadata` ([#1170](https://github.com/0xMiden/miden-base/pull/1170)). -- [BREAKING] Rework miden-lib error codes into categories ([#1196](https://github.com/0xMiden/miden-base/pull/1196)). -- [BREAKING] Moved the `TransactionScriptBuilder` from `miden-client` to `miden-base` ([#1206](https://github.com/0xMiden/miden-base/pull/1206)). -- [BREAKING] Enable timestamp customization on `MockChain::seal_block` ([#1208](https://github.com/0xMiden/miden-base/pull/1208)). -- [BREAKING] Renamed constants and comments: `OnChain` -> `Public` and `OffChain` -> `Private` ([#1218](https://github.com/0xMiden/miden-base/pull/1218)). -- [BREAKING] Replace "hash" with "commitment" in `BlockHeader::{prev_hash, chain_root, kernel_root, tx_hash, proof_hash, sub_hash, hash}` ([#1209](https://github.com/0xMiden/miden-base/pull/1209), [#1221](https://github.com/0xMiden/miden-base/pull/1221), [#1226](https://github.com/0xMiden/miden-base/pull/1226)). -- [BREAKING] Incremented minimum supported Rust version to 1.85. -- [BREAKING] Change advice for Falcon signature verification ([#1183](https://github.com/0xMiden/miden-base/pull/1183)). -- Added `info` log level by default in the proving service ([#1200](https://github.com/0xMiden/miden-base/pull/1200)). -- Made Prometheus metrics optional in the proving service proxy via the `enable_metrics` configuration option ([#1200](https://github.com/0xMiden/miden-base/pull/1200)). -- Improved logging in the proving service proxy for better diagnostics ([#1200](https://github.com/0xMiden/miden-base/pull/1200)). -- Fixed issues with the proving service proxy's signal handling and port binding ([#1200](https://github.com/0xMiden/miden-base/pull/1200)). -- [BREAKING] Simplified worker update configuration by using a single URL parameter instead of separate host and port ([#1249](https://github.com/0xMiden/miden-base/pull/1249)). - -## 0.7.2 (2025-01-28) - `miden-objects` crate only - -### Changes - -- Added serialization for `ExecutedTransaction` ([#1113](https://github.com/0xMiden/miden-base/pull/1113)). - -## 0.7.1 (2025-01-24) - `miden-objects` crate only - -### Fixes - -- Added missing doc comments ([#1100](https://github.com/0xMiden/miden-base/pull/1100)). -- Fixed setting of supporting types when instantiating `AccountComponent` from templates ([#1103](https://github.com/0xMiden/miden-base/pull/1103)). - -## 0.7.0 (2025-01-22) - -### Highlights - -- [BREAKING] Extend `AccountId` to two `Felt`s and require block hash in derivation ([#982](https://github.com/0xMiden/miden-base/pull/982)). -- Introduced `AccountComponentTemplate` with TOML serialization and templating ([#1015](https://github.com/0xMiden/miden-base/pull/1015), [#1027](https://github.com/0xMiden/miden-base/pull/1027)). -- Introduce `AccountIdBuilder` to simplify `AccountId` generation in tests ([#1045](https://github.com/0xMiden/miden-base/pull/1045)). -- [BREAKING] Migrate to the element-addressable memory ([#1084](https://github.com/0xMiden/miden-base/pull/1084)). - -### Changes - -- Implemented serialization for `AccountHeader` ([#996](https://github.com/0xMiden/miden-base/pull/996)). -- Updated Pingora crates to 0.4 and added polling time to the configuration file ([#997](https://github.com/0xMiden/miden-base/pull/997)). -- Added support for `miden-tx-prover` proxy to update workers on a running proxy ([#989](https://github.com/0xMiden/miden-base/pull/989)). -- Refactored `miden-tx-prover` proxy load balancing strategy ([#976](https://github.com/0xMiden/miden-base/pull/976)). -- [BREAKING] Implemented better error display when queues are full in the prover service ([#967](https://github.com/0xMiden/miden-base/pull/967)). -- [BREAKING] Removed `AccountBuilder::build_testing` and make `Account::initialize_from_components` private ([#969](https://github.com/0xMiden/miden-base/pull/969)). -- [BREAKING] Added error messages to errors and implement `core::error::Error` ([#974](https://github.com/0xMiden/miden-base/pull/974)). -- Implemented new `digest!` macro ([#984](https://github.com/0xMiden/miden-base/pull/984)). -- Added Format Guidebook to the `miden-lib` crate ([#987](https://github.com/0xMiden/miden-base/pull/987)). -- Added conversion from `Account` to `AccountDelta` for initial account state representation as delta ([#983](https://github.com/0xMiden/miden-base/pull/983)). -- [BREAKING] Added `miden::note::get_script_hash` procedure ([#995](https://github.com/0xMiden/miden-base/pull/995)). -- [BREAKING] Refactor error messages in `miden-lib` and `miden-tx` and use `thiserror` 2.0 ([#1005](https://github.com/0xMiden/miden-base/pull/1005), [#1090](https://github.com/0xMiden/miden-base/pull/1090)). -- Added health check endpoints to the prover service ([#1006](https://github.com/0xMiden/miden-base/pull/1006)). -- Removed workers list from the proxy configuration file ([#1018](https://github.com/0xMiden/miden-base/pull/1018)). -- Added tracing to the `miden-tx-prover` CLI ([#1014](https://github.com/0xMiden/miden-base/pull/1014)). -- Added metrics to the `miden-tx-prover` proxy ([#1017](https://github.com/0xMiden/miden-base/pull/1017)). -- Implemented `to_hex` for `AccountIdPrefix` and `epoch_block_num` for `BlockHeader` ([#1039](https://github.com/0xMiden/miden-base/pull/1039)). -- [BREAKING] Updated the names and values of the kernel procedure offsets and corresponding kernel procedures ([#1037](https://github.com/0xMiden/miden-base/pull/1037)). -- Introduce `AccountIdError` and make account ID byte representations (`u128`, `[u8; 15]`) consistent ([#1055](https://github.com/0xMiden/miden-base/pull/1055)). -- Refactor `AccountId` and `AccountIdPrefix` into version wrappers ([#1058](https://github.com/0xMiden/miden-base/pull/1058)). -- Remove multi-threaded account seed generation due to single-threaded generation being faster ([#1061](https://github.com/0xMiden/miden-base/pull/1061)). -- Made `AccountIdError` public ([#1067](https://github.com/0xMiden/miden-base/pull/1067)). -- Made `BasicFungibleFaucet::MAX_DECIMALS` public ([#1063](https://github.com/0xMiden/miden-base/pull/1063)). -- [BREAKING] Removed `miden-tx-prover` crate and created `miden-proving-service` and `miden-proving-service-client` ([#1047](https://github.com/0xMiden/miden-base/pull/1047)). -- Removed deduplicate `masm` procedures across kernel and miden lib to a shared `util` module ([#1070](https://github.com/0xMiden/miden-base/pull/1070)). -- [BREAKING] Added `BlockNumber` struct ([#1043](https://github.com/0xMiden/miden-base/pull/1043), [#1080](https://github.com/0xMiden/miden-base/pull/1080), [#1082](https://github.com/0xMiden/miden-base/pull/1082)). -- [BREAKING] Removed `GENESIS_BLOCK` public constant ([#1088](https://github.com/0xMiden/miden-base/pull/1088)). -- Add CI check for unused dependencies ([#1075](https://github.com/0xMiden/miden-base/pull/1075)). -- Added storage placeholder types and support for templated map ([#1074](https://github.com/0xMiden/miden-base/pull/1074)). -- [BREAKING] Move crates into `crates/` and rename plural modules to singular ([#1091](https://github.com/0xMiden/miden-base/pull/1091)). - -## 0.6.2 (2024-11-20) - -- Avoid writing to the filesystem during docs.rs build ([#970](https://github.com/0xMiden/miden-base/pull/970)). - -## 0.6.1 (2024-11-08) - -### Features - -- [BREAKING] Added CLI for the transaction prover services both the workers and the proxy ([#955](https://github.com/0xMiden/miden-base/pull/955)). ### Fixes -- Fixed `AccountId::new_with_type_and_mode()` ([#958](https://github.com/0xMiden/miden-base/pull/958)). -- Updated the ABI for the assembly procedures ([#971](https://github.com/0xMiden/miden-base/pull/971)). - -## 0.6.0 (2024-11-05) - -### Features - -- Created a proving service that receives `TransactionWitness` and returns the proof using gRPC ([#881](https://github.com/0xMiden/miden-base/pull/881)). -- Implemented ability to invoke procedures against the foreign account ([#882](https://github.com/0xMiden/miden-base/pull/882), [#890](https://github.com/0xMiden/miden-base/pull/890), [#896](https://github.com/0xMiden/miden-base/pull/896)). -- Implemented kernel procedure to set transaction expiration block delta ([#897](https://github.com/0xMiden/miden-base/pull/897)). -- [BREAKING] Introduce a new way to build `Account`s from `AccountComponent`s ([#941](https://github.com/0xMiden/miden-base/pull/941)). -- [BREAKING] Introduce an `AccountBuilder` ([#952](https://github.com/0xMiden/miden-base/pull/952)). - -### Changes - -- [BREAKING] Changed `TransactionExecutor` and `TransactionHost` to use trait objects ([#897](https://github.com/0xMiden/miden-base/pull/897)). -- Made note scripts public ([#880](https://github.com/0xMiden/miden-base/pull/880)). -- Implemented serialization for `TransactionWitness`, `ChainMmr`, `TransactionInputs` and `TransactionArgs` ([#888](https://github.com/0xMiden/miden-base/pull/888)). -- [BREAKING] Renamed the `TransactionProver` struct to `LocalTransactionProver` and added the `TransactionProver` trait ([#865](https://github.com/0xMiden/miden-base/pull/865)). -- Implemented `Display`, `TryFrom<&str>` and `FromStr` for `AccountStorageMode` ([#861](https://github.com/0xMiden/miden-base/pull/861)). -- Implemented offset based storage access ([#843](https://github.com/0xMiden/miden-base/pull/843)). -- [BREAKING] `AccountStorageType` enum was renamed to `AccountStorageMode` along with its variants ([#854](https://github.com/0xMiden/miden-base/pull/854)). -- [BREAKING] `AccountStub` structure was renamed to `AccountHeader` ([#855](https://github.com/0xMiden/miden-base/pull/855)). -- [BREAKING] Kernel procedures now have to be invoked using `dynexec` instruction ([#803](https://github.com/0xMiden/miden-base/pull/803)). -- Refactored `AccountStorage` from `Smt` to sequential hash ([#846](https://github.com/0xMiden/miden-base/pull/846)). -- [BREAKING] Refactored batch/block note trees ([#834](https://github.com/0xMiden/miden-base/pull/834)). -- Set all procedures storage offsets of faucet accounts to `1` ([#875](https://github.com/0xMiden/miden-base/pull/875)). -- Added `AccountStorageHeader` ([#876](https://github.com/0xMiden/miden-base/pull/876)). -- Implemented generation of transaction kernel procedure hashes in build.rs ([#887](https://github.com/0xMiden/miden-base/pull/887)). -- [BREAKING] `send_asset` procedure was removed from the basic wallet ([#829](https://github.com/0xMiden/miden-base/pull/829)). -- [BREAKING] Updated limits, introduced additional limits ([#889](https://github.com/0xMiden/miden-base/pull/889)). -- Introduced `AccountDelta` maximum size limit of 32 KiB ([#889](https://github.com/0xMiden/miden-base/pull/889)). -- [BREAKING] Moved `MAX_NUM_FOREIGN_ACCOUNTS` into `miden-objects` ([#904](https://github.com/0xMiden/miden-base/pull/904)). -- Implemented `storage_size`, updated storage bounds ([#886](https://github.com/0xMiden/miden-base/pull/886)). -- [BREAKING] Auto-generate `KERNEL_ERRORS` list from the transaction kernel's MASM files and rework error constant names ([#906](https://github.com/0xMiden/miden-base/pull/906)). -- Implement `Serializable` for `FungibleAsset` ([#907](https://github.com/0xMiden/miden-base/pull/907)). -- [BREAKING] Changed `TransactionProver` trait to be `maybe_async_trait` based on the `async` feature ([#913](https://github.com/0xMiden/miden-base/pull/913)). -- [BREAKING] Changed type of `EMPTY_STORAGE_MAP_ROOT` constant to `RpoDigst`, which references constant from `miden-crypto` ([#916](https://github.com/0xMiden/miden-base/pull/916)). -- Added `RemoteTransactionProver` struct to `miden-tx-prover` ([#921](https://github.com/0xMiden/miden-base/pull/921)). -- [BREAKING] Migrated to v0.11 version of Miden VM ([#929](https://github.com/0xMiden/miden-base/pull/929)). -- Added `total_cycles` and `trace_length` to the `TransactionMeasurements` ([#953](https://github.com/0xMiden/miden-base/pull/953)). -- Added ability to load libraries into `TransactionExecutor` and `LocalTransactionProver` ([#954](https://github.com/0xMiden/miden-base/pull/954)). - -## 0.5.1 (2024-08-28) - `miden-objects` crate only - -- Implemented `PrettyPrint` and `Display` for `NoteScript`. - -## 0.5.0 (2024-08-27) - -### Features - -- [BREAKING] Increase of nonce does not require changes in account state any more ([#796](https://github.com/0xMiden/miden-base/pull/796)). -- Changed `AccountCode` procedures from merkle tree to sequential hash + added storage_offset support ([#763](https://github.com/0xMiden/miden-base/pull/763)). -- Implemented merging of account deltas ([#797](https://github.com/0xMiden/miden-base/pull/797)). -- Implemented `create_note` and `move_asset_into_note` basic wallet procedures ([#808](https://github.com/0xMiden/miden-base/pull/808)). -- Made `miden_lib::notes::build_swap_tag()` function public ([#817](https://github.com/0xMiden/miden-base/pull/817)). -- [BREAKING] Changed the `NoteFile::NoteDetails` type to struct and added a `after_block_num` field ([#823](https://github.com/0xMiden/miden-base/pull/823)). - -### Changes - -- Renamed "consumed" and "created" notes into "input" and "output" respectively ([#791](https://github.com/0xMiden/miden-base/pull/791)). -- [BREAKING] Renamed `NoteType::OffChain` into `NoteType::Private`. -- [BREAKING] Renamed public accessors of the `Block` struct to match the updated fields ([#791](https://github.com/0xMiden/miden-base/pull/791)). -- [BREAKING] Changed the `TransactionArgs` to use `AdviceInputs` ([#793](https://github.com/0xMiden/miden-base/pull/793)). -- Setters in `memory` module don't drop the setting `Word` anymore ([#795](https://github.com/0xMiden/miden-base/pull/795)). -- Added `CHANGELOG.md` warning message on CI ([#799](https://github.com/0xMiden/miden-base/pull/799)). -- Added high-level methods for `MockChain` and related structures ([#807](https://github.com/0xMiden/miden-base/pull/807)). -- [BREAKING] Renamed `NoteExecutionHint` to `NoteExecutionMode` and added new `NoteExecutionHint` to `NoteMetadata` ([#812](https://github.com/0xMiden/miden-base/pull/812), [#816](https://github.com/0xMiden/miden-base/pull/816)). -- [BREAKING] Changed the interface of the `miden::tx::add_asset_to_note` ([#808](https://github.com/0xMiden/miden-base/pull/808)). -- [BREAKING] Refactored and simplified `NoteOrigin` and `NoteInclusionProof` structs ([#810](https://github.com/0xMiden/miden-base/pull/810), [#814](https://github.com/0xMiden/miden-base/pull/814)). -- [BREAKING] Refactored account storage and vault deltas ([#822](https://github.com/0xMiden/miden-base/pull/822)). -- Added serialization and equality comparison for `TransactionScript` ([#824](https://github.com/0xMiden/miden-base/pull/824)). -- [BREAKING] Migrated to Miden VM v0.10 ([#826](https://github.com/0xMiden/miden-base/pull/826)). -- Added conversions for `NoteExecutionHint` ([#827](https://github.com/0xMiden/miden-base/pull/827)). -- [BREAKING] Removed `serde`-based serialization from `miden-object` structs ([#838](https://github.com/0xMiden/miden-base/pull/838)). - -## 0.4.0 (2024-07-03) - -### Features - -- [BREAKING] Introduce `OutputNote::Partial` variant ([#698](https://github.com/0xMiden/miden-base/pull/698)). -- [BREAKING] Added support for input notes with delayed verification of inclusion proofs ([#724](https://github.com/0xMiden/miden-base/pull/724), [#732](https://github.com/0xMiden/miden-base/pull/732), [#759](https://github.com/0xMiden/miden-base/pull/759), [#770](https://github.com/0xMiden/miden-base/pull/770), [#772](https://github.com/0xMiden/miden-base/pull/772)). -- Added new `NoteFile` object to represent serialized notes ([#721](https://github.com/0xMiden/miden-base/pull/721)). -- Added transaction IDs to the `Block` struct ([#734](https://github.com/0xMiden/miden-base/pull/734)). -- Added ability for users to set the aux field when creating a note ([#752](https://github.com/0xMiden/miden-base/pull/752)). - -### Enhancements - -- Replaced `cargo-make` with just `make` for running tasks ([#696](https://github.com/0xMiden/miden-base/pull/696)). -- [BREAKING] Split `Account` struct constructor into `new()` and `from_parts()` ([#699](https://github.com/0xMiden/miden-base/pull/699)). -- Generalized `build_recipient_hash` procedure to build recipient hash for custom notes ([#706](https://github.com/0xMiden/miden-base/pull/706)). -- [BREAKING] Changed the encoding of inputs notes in the advice map for consumed notes ([#707](https://github.com/0xMiden/miden-base/pull/707)). -- Created additional `emit` events for kernel related `.masm` procedures ([#708](https://github.com/0xMiden/miden-base/pull/708)). -- Implemented `build_recipient_hash` procedure to build recipient hash for custom notes ([#710](https://github.com/0xMiden/miden-base/pull/710)). -- Removed the `mock` crate in favor of having mock code behind the `testing` flag in remaining crates ([#711](https://github.com/0xMiden/miden-base/pull/711)). -- [BREAKING] Created `auth` module for `TransactionAuthenticator` and other related objects ([#714](https://github.com/0xMiden/miden-base/pull/714)). -- Added validation for the output stack to make sure it was properly cleaned ([#717](https://github.com/0xMiden/miden-base/pull/717)). -- Made `DataStore` conditionally async using `winter-maybe-async` ([#725](https://github.com/0xMiden/miden-base/pull/725)). -- Changed note pointer from Memory `note_ptr` to `note_index` ([#728](https://github.com/0xMiden/miden-base/pull/728)). -- [BREAKING] Changed rng to mutable reference in note creation functions ([#733](https://github.com/0xMiden/miden-base/pull/733)). -- [BREAKING] Replaced `ToNullifier` trait with `ToInputNoteCommitments`, which includes the `note_id` for delayed note authentication ([#732](https://github.com/0xMiden/miden-base/pull/732)). -- Added `Option`to `NoteFile` ([#741](https://github.com/0xMiden/miden-base/pull/741)). -- Fixed documentation and added `make doc` CI job ([#746](https://github.com/0xMiden/miden-base/pull/746)). -- Updated and improved [.pre-commit-config.yaml](.pre-commit-config.yaml) file ([#748](https://github.com/0xMiden/miden-base/pull/748)). -- Created `get_serial_number` procedure to get the serial num of the currently processed note ([#760](https://github.com/0xMiden/miden-base/pull/760)). -- [BREAKING] Added support for conversion from `Nullifier` to `InputNoteCommitment`, commitment header return reference ([#774](https://github.com/0xMiden/miden-base/pull/774)). -- Added `compute_inputs_hash` procedure for hash computation of the arbitrary number of note inputs ([#750](https://github.com/0xMiden/miden-base/pull/750)). - -## 0.3.1 (2024-06-12) - -- Replaced `cargo-make` with just `make` for running tasks ([#696](https://github.com/0xMiden/miden-base/pull/696)). -- Made `DataStore` conditionally async using `winter-maybe-async` ([#725](https://github.com/0xMiden/miden-base/pull/725)) -- Fixed `StorageMap`s implementation and included into apply_delta ([#745](https://github.com/0xMiden/miden-base/pull/745)) - -## 0.3.0 (2024-05-14) - -- Introduce the `miden-bench-tx` crate used for transactions benchmarking ([#577](https://github.com/0xMiden/miden-base/pull/577)). -- [BREAKING] Removed the transaction script root output from the transaction kernel ([#608](https://github.com/0xMiden/miden-base/pull/608)). -- [BREAKING] Refactored account update details, moved `Block` to `miden-objects` ([#618](https://github.com/0xMiden/miden-base/pull/618), [#621](https://github.com/0xMiden/miden-base/pull/621)). -- [BREAKING] Made `TransactionExecutor` generic over `TransactionAuthenticator` ([#628](https://github.com/0xMiden/miden-base/pull/628)). -- [BREAKING] Changed type of `version` and `timestamp` fields to `u32`, moved `version` to the beginning of block header ([#639](https://github.com/0xMiden/miden-base/pull/639)). -- [BREAKING] Renamed `NoteEnvelope` into `NoteHeader` and introduced `NoteDetails` ([#664](https://github.com/0xMiden/miden-base/pull/664)). -- [BREAKING] Updated `create_swap_note()` procedure to return `NoteDetails` and defined SWAP note tag format ([#665](https://github.com/0xMiden/miden-base/pull/665)). -- Implemented `OutputNoteBuilder` ([#669](https://github.com/0xMiden/miden-base/pull/669)). -- [BREAKING] Added support for full details of private notes, renamed `OutputNote` variants and changed their meaning ([#673](https://github.com/0xMiden/miden-base/pull/673)). -- [BREAKING] Added `add_asset_to_note` procedure to the transaction kernel ([#674](https://github.com/0xMiden/miden-base/pull/674)). -- Made `TransactionArgs::add_expected_output_note()` more flexible ([#681](https://github.com/0xMiden/miden-base/pull/681)). -- [BREAKING] Enabled support for notes without assets and refactored `create_note` procedure in the transaction kernel ([#686](https://github.com/0xMiden/miden-base/pull/686)). - -## 0.2.3 (2024-04-26) - `miden-tx` crate only - -- Fixed handling of debug mode in `TransactionExecutor` ([#627](https://github.com/0xMiden/miden-base/pull/627)) - -## 0.2.2 (2024-04-23) - `miden-tx` crate only - -- Added `with_debug_mode()` methods to `TransactionCompiler` and `TransactionExecutor` ([#562](https://github.com/0xMiden/miden-base/pull/562)). - -## 0.2.1 (2024-04-12) - -- [BREAKING] Return a reference to `NoteMetadata` from output notes ([#593](https://github.com/0xMiden/miden-base/pull/593)). -- Add more type conversions for `NoteType` ([#597](https://github.com/0xMiden/miden-base/pull/597)). -- Fix note input padding for expected output notes ([#598](https://github.com/0xMiden/miden-base/pull/598)). - -## 0.2.0 (2024-04-11) - -- [BREAKING] Implement support for public accounts ([#481](https://github.com/0xMiden/miden-base/pull/481), [#485](https://github.com/0xMiden/miden-base/pull/485), [#538](https://github.com/0xMiden/miden-base/pull/538)). -- [BREAKING] Implement support for public notes ([#515](https://github.com/0xMiden/miden-base/pull/515), [#540](https://github.com/0xMiden/miden-base/pull/540), [#572](https://github.com/0xMiden/miden-base/pull/572)). -- Improved `ProvenTransaction` validation ([#532](https://github.com/0xMiden/miden-base/pull/532)). -- [BREAKING] Updated `no-std` setup ([#533](https://github.com/0xMiden/miden-base/pull/533)). -- Improved `ProvenTransaction` serialization ([#543](https://github.com/0xMiden/miden-base/pull/543)). -- Implemented note tree wrapper structs ([#560](https://github.com/0xMiden/miden-base/pull/560)). -- [BREAKING] Migrated to v0.9 version of Miden VM ([#567](https://github.com/0xMiden/miden-base/pull/567)). -- [BREAKING] Added account storage type parameter to `create_basic_wallet` and `create_basic_fungible_faucet` (miden-lib - crate only) ([#587](https://github.com/0xMiden/miden-base/pull/587)). -- Removed serialization of source locations from account code ([#590](https://github.com/0xMiden/miden-base/pull/590)). - -## 0.1.1 (2024-03-07) - `miden-objects` crate only - -- Added `BlockHeader::mock()` method ([#511](https://github.com/0xMiden/miden-base/pull/511)) - -## 0.1.0 (2024-03-05) - -- Initial release. +- Fixed `PartialAccountTree::track_account` handling of empty leaves ([#2598](https://github.com/0xMiden/protocol/pull/2598)). \ No newline at end of file diff --git a/crates/miden-protocol/src/asset/token_symbol.rs b/crates/miden-protocol/src/asset/token_symbol.rs index 7189d6805b..34ed9d445b 100644 --- a/crates/miden-protocol/src/asset/token_symbol.rs +++ b/crates/miden-protocol/src/asset/token_symbol.rs @@ -144,6 +144,7 @@ impl TryFrom for TokenSymbol { if encoded_value < Self::MIN_ENCODED_VALUE { return Err(TokenSymbolError::ValueTooSmall(encoded_value)); } + if encoded_value > Self::MAX_ENCODED_VALUE { return Err(TokenSymbolError::ValueTooLarge(encoded_value)); } diff --git a/crates/miden-standards/asm/account_components/faucets/basic_fungible_faucet.masm b/crates/miden-standards/asm/account_components/faucets/basic_fungible_faucet.masm index 37895b9ef7..d33f7cb798 100644 --- a/crates/miden-standards/asm/account_components/faucets/basic_fungible_faucet.masm +++ b/crates/miden-standards/asm/account_components/faucets/basic_fungible_faucet.masm @@ -1,6 +1,7 @@ # The MASM code of the Basic Fungible Faucet Account Component. # # See the `BasicFungibleFaucet` Rust type's documentation for more details. +# This component depends on `FungibleTokenMetadata` being present in the account. pub use ::miden::standards::faucets::basic_fungible::mint_and_send pub use ::miden::standards::faucets::basic_fungible::burn diff --git a/crates/miden-standards/asm/account_components/faucets/fungible_token_metadata.masm b/crates/miden-standards/asm/account_components/faucets/fungible_token_metadata.masm new file mode 100644 index 0000000000..bb39df9737 --- /dev/null +++ b/crates/miden-standards/asm/account_components/faucets/fungible_token_metadata.masm @@ -0,0 +1,20 @@ +# The MASM code of the Fungible Token Metadata Account Component. +# +# Re-exported from module miden::standards::components::fungible_token_metadata +# All metadata-related procedures are re-exported here. + +pub use ::miden::standards::metadata::fungible_faucet::get_name +pub use ::miden::standards::metadata::fungible_faucet::get_mutability_config +pub use ::miden::standards::metadata::fungible_faucet::is_max_supply_mutable +pub use ::miden::standards::metadata::fungible_faucet::is_description_mutable +pub use ::miden::standards::metadata::fungible_faucet::is_logo_uri_mutable +pub use ::miden::standards::metadata::fungible_faucet::is_external_link_mutable +pub use ::miden::standards::metadata::fungible_faucet::get_token_metadata +pub use ::miden::standards::metadata::fungible_faucet::get_max_supply +pub use ::miden::standards::metadata::fungible_faucet::get_decimals +pub use ::miden::standards::metadata::fungible_faucet::get_token_symbol +pub use ::miden::standards::metadata::fungible_faucet::get_token_supply +pub use ::miden::standards::metadata::fungible_faucet::set_description +pub use ::miden::standards::metadata::fungible_faucet::set_logo_uri +pub use ::miden::standards::metadata::fungible_faucet::set_external_link +pub use ::miden::standards::metadata::fungible_faucet::set_max_supply diff --git a/crates/miden-standards/asm/account_components/faucets/network_fungible_faucet.masm b/crates/miden-standards/asm/account_components/faucets/network_fungible_faucet.masm index 0aee492d62..8b6d4064af 100644 --- a/crates/miden-standards/asm/account_components/faucets/network_fungible_faucet.masm +++ b/crates/miden-standards/asm/account_components/faucets/network_fungible_faucet.masm @@ -1,6 +1,7 @@ # The MASM code of the Network Fungible Faucet Account Component. # # See the `NetworkFungibleFaucet` Rust type's documentation for more details. +# This component depends on `FungibleTokenMetadata` being present in the account. pub use ::miden::standards::faucets::network_fungible::mint_and_send pub use ::miden::standards::faucets::network_fungible::burn diff --git a/crates/miden-standards/asm/standards/access/ownable.masm b/crates/miden-standards/asm/standards/access/ownable.masm deleted file mode 100644 index b0591e71a5..0000000000 --- a/crates/miden-standards/asm/standards/access/ownable.masm +++ /dev/null @@ -1,162 +0,0 @@ -# miden::standards::access::ownable -# -# Provides ownership management functionality for account components. -# This template can be imported and used by any component that needs owner controls. - -use miden::protocol::active_account -use miden::protocol::account_id -use miden::protocol::active_note -use miden::protocol::native_account - -# CONSTANTS -# ================================================================================================ - -# The slot in this component's storage layout where the owner config is stored. -const OWNER_CONFIG_SLOT = word("miden::standards::access::ownable::owner_config") - -# ZERO_ADDRESS word (all zeros) used to represent no owner -# Layout: [suffix=0, prefix=0, 0, 0] as stored in account storage -const ZERO_ADDRESS = [0, 0, 0, 0] - -# ERRORS -# ================================================================================================ - -const ERR_SENDER_NOT_OWNER = "note sender is not the owner" - -# INTERNAL PROCEDURES -# ================================================================================================ - -#! Returns the owner AccountId from storage. -#! -#! Inputs: [] -#! Outputs: [owner_suffix, owner_prefix] -#! -#! Where: -#! - owner_{suffix, prefix} are the suffix and prefix felts of the owner AccountId. -proc owner - push.OWNER_CONFIG_SLOT[0..2] exec.active_account::get_item - # => [0, 0, owner_suffix, owner_prefix] - - drop drop - # => [owner_suffix, owner_prefix] -end - -#! Checks if the given account ID is the owner of this component. -#! -#! Inputs: [account_id_suffix, account_id_prefix] -#! Outputs: [is_owner] -#! -#! Where: -#! - account_id_{suffix, prefix} are the suffix and prefix felts of the AccountId to check. -#! - is_owner is 1 if the account is the owner, 0 otherwise. -proc is_owner - exec.owner - # => [owner_suffix, owner_prefix, account_id_suffix, account_id_prefix] - - exec.account_id::is_equal - # => [is_owner] -end - -# PUBLIC INTERFACE -# ================================================================================================ - -#! Checks if the note sender is the owner and panics if not. -#! -#! Inputs: [] -#! Outputs: [] -#! -#! Panics if: -#! - the note sender is not the owner. -pub proc verify_owner - exec.active_note::get_sender - # => [sender_suffix, sender_prefix] - - exec.is_owner - # => [is_owner] - - assert.err=ERR_SENDER_NOT_OWNER - # => [] -end - -#! Returns the owner AccountId. -#! -#! Inputs: [pad(16)] -#! Outputs: [owner_suffix, owner_prefix, pad(14)] -#! -#! Where: -#! - owner_{suffix, prefix} are the suffix and prefix felts of the owner AccountId. -#! -#! Invocation: call -pub proc get_owner - exec.owner - # => [owner_suffix, owner_prefix, pad(14)] -end - -#! Transfers ownership to a new account. -#! -#! Can only be called by the current owner. -#! -#! Inputs: [new_owner_suffix, new_owner_prefix, pad(14)] -#! Outputs: [pad(16)] -#! -#! Where: -#! - new_owner_{suffix, prefix} are the suffix and prefix felts of the new owner AccountId. -#! -#! Panics if: -#! - the note sender is not the owner. -#! -#! Invocation: call -pub proc transfer_ownership - # Check that the caller is the owner - exec.verify_owner - # => [new_owner_suffix, new_owner_prefix, pad(14)] - - push.0.0 - # => [0, 0, new_owner_suffix, new_owner_prefix, pad(14)] - - push.OWNER_CONFIG_SLOT[0..2] - # => [slot_suffix, slot_prefix, 0, 0, new_owner_suffix, new_owner_prefix, pad(14)] - - exec.native_account::set_item - # => [OLD_OWNER_WORD, pad(14)] - - # When the stack has 16 elements, dropw will shift in zeros from the right, - # resulting in [pad(16)]. So dropw is sufficient here. - dropw - # => [pad(16)] -end - -#! Renounces ownership, leaving the component without an owner. -#! -#! Can only be called by the current owner. -#! -#! Inputs: [pad(16)] -#! Outputs: [pad(16)] -#! -#! Panics if: -#! - the note sender is not the owner. -#! -#! Invocation: call -#! -#! Important Note! -#! This feature allows the owner to relinquish administrative privileges, a common pattern -#! after an initial stage with centralized administration is over. Once ownership is renounced, -#! the component becomes permanently ownerless and cannot be managed by any account. -pub proc renounce_ownership - exec.verify_owner - # => [pad(16)] - - # ---- Push ZERO_ADDRESS to storage ---- - push.ZERO_ADDRESS - # => [0, 0, 0, 0, pad(16)] - - push.OWNER_CONFIG_SLOT[0..2] - # => [slot_suffix, slot_prefix, 0, 0, 0, 0, pad(16)] - - exec.native_account::set_item - # => [OLD_OWNER_WORD, pad(16)] - - dropw - # => [pad(16)] -end - diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index 57eaf2b416..6da0c9d963 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -5,14 +5,15 @@ use miden::protocol::native_account use miden::protocol::output_note use miden::protocol::asset use miden::protocol::asset::FUNGIBLE_ASSET_MAX_AMOUNT +use miden::standards::metadata::fungible_faucet::TOKEN_METADATA_SLOT -# CONSTANTS +# CONSTANTS # ================================================================================================= const ASSET_PTR=0 const PRIVATE_NOTE=2 -# ERRORS +# ERRORS # ================================================================================================= const ERR_FUNGIBLE_ASSET_TOKEN_SUPPLY_EXCEEDS_MAX_SUPPLY="token supply exceeds max supply" @@ -31,9 +32,6 @@ const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 no # The local memory address at which the metadata slot content is stored. const METADATA_SLOT_LOCAL=0 -# The standard slot where fungible faucet metadata like token symbol or decimals are stored. -# Layout: [token_supply, max_supply, decimals, token_symbol] -const METADATA_SLOT=word("miden::standards::fungible_faucets::metadata") #! Mints fungible assets to the provided recipient by creating a note. #! @@ -60,7 +58,7 @@ pub proc mint_and_send # Get the configured max supply and the token supply (= current supply). # --------------------------------------------------------------------------------------------- - push.METADATA_SLOT[0..2] exec.active_account::get_item + push.TOKEN_METADATA_SLOT[0..2] exec.active_account::get_item # => [token_supply, max_supply, decimals, token_symbol, amount, tag, note_type, RECIPIENT] # store a copy of the current slot content for the token_supply update later @@ -120,7 +118,7 @@ pub proc mint_and_send # => [[new_token_supply, max_supply, decimals, token_symbol], amount, tag, note_type, RECIPIENT] # update the metadata slot with the new supply - push.METADATA_SLOT[0..2] exec.native_account::set_item dropw + push.TOKEN_METADATA_SLOT[0..2] exec.native_account::set_item dropw # => [amount, tag, note_type, RECIPIENT] # Create a new note. @@ -204,7 +202,7 @@ pub proc burn # Subtract burnt amount from current token_supply in storage. # --------------------------------------------------------------------------------------------- - push.METADATA_SLOT[0..2] exec.active_account::get_item + push.TOKEN_METADATA_SLOT[0..2] exec.active_account::get_item # => [token_supply, max_supply, decimals, token_symbol, amount, pad(16)] dup.4 dup.1 @@ -222,6 +220,6 @@ pub proc burn # => [new_token_supply, max_supply, decimals, token_symbol, pad(16)] # update the metadata slot with the new supply - push.METADATA_SLOT[0..2] exec.native_account::set_item dropw + push.TOKEN_METADATA_SLOT[0..2] exec.native_account::set_item dropw # => [pad(16)] end diff --git a/crates/miden-standards/asm/standards/metadata/fungible_faucet.masm b/crates/miden-standards/asm/standards/metadata/fungible_faucet.masm new file mode 100644 index 0000000000..a0a10af51a --- /dev/null +++ b/crates/miden-standards/asm/standards/metadata/fungible_faucet.masm @@ -0,0 +1,616 @@ +# miden::standards::metadata::fungible_faucet +# +# Metadata for fungible-style accounts: slots, getters (name, description, logo_uri, +# external_link, token_metadata), and owner-gated setters. +# Depends on ownable2step for ownership checks. + +use miden::core::mem +use miden::protocol::active_account +use miden::protocol::native_account +use miden::standards::access::ownable2step + +# ================================================================================================= +# CONSTANTS — slot names +# ================================================================================================= + +pub const TOKEN_METADATA_SLOT = word("miden::standards::metadata::fungible_faucet::token_metadata") +const NAME_CHUNK_0_SLOT = word("miden::standards::metadata::fungible_faucet::name_chunk_0") +const NAME_CHUNK_1_SLOT = word("miden::standards::metadata::fungible_faucet::name_chunk_1") +const MUTABILITY_CONFIG_SLOT = word("miden::standards::metadata::fungible_faucet::mutability_config") + +const DESCRIPTION_0_SLOT = word("miden::standards::metadata::fungible_faucet::description_0") +const DESCRIPTION_1_SLOT = word("miden::standards::metadata::fungible_faucet::description_1") +const DESCRIPTION_2_SLOT = word("miden::standards::metadata::fungible_faucet::description_2") +const DESCRIPTION_3_SLOT = word("miden::standards::metadata::fungible_faucet::description_3") +const DESCRIPTION_4_SLOT = word("miden::standards::metadata::fungible_faucet::description_4") +const DESCRIPTION_5_SLOT = word("miden::standards::metadata::fungible_faucet::description_5") +const DESCRIPTION_6_SLOT = word("miden::standards::metadata::fungible_faucet::description_6") + +const LOGO_URI_0_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_0") +const LOGO_URI_1_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_1") +const LOGO_URI_2_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_2") +const LOGO_URI_3_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_3") +const LOGO_URI_4_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_4") +const LOGO_URI_5_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_5") +const LOGO_URI_6_SLOT = word("miden::standards::metadata::fungible_faucet::logo_uri_6") + +const EXTERNAL_LINK_0_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_0") +const EXTERNAL_LINK_1_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_1") +const EXTERNAL_LINK_2_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_2") +const EXTERNAL_LINK_3_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_3") +const EXTERNAL_LINK_4_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_4") +const EXTERNAL_LINK_5_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_5") +const EXTERNAL_LINK_6_SLOT = word("miden::standards::metadata::fungible_faucet::external_link_6") + +const FIELD_0_LOC = 0 +const FIELD_1_LOC = 4 +const FIELD_2_LOC = 8 +const FIELD_3_LOC = 12 +const FIELD_4_LOC = 16 +const FIELD_5_LOC = 20 +const FIELD_6_LOC = 24 + +const ERR_DESCRIPTION_NOT_MUTABLE = "description is not mutable" +const ERR_LOGO_URI_NOT_MUTABLE = "logo URI is not mutable" +const ERR_EXTERNAL_LINK_NOT_MUTABLE = "external link is not mutable" +const ERR_MAX_SUPPLY_NOT_MUTABLE = "max supply is not mutable" +const ERR_NEW_MAX_SUPPLY_BELOW_TOKEN_SUPPLY = "new max supply is less than current token supply" + +# ================================================================================================= +# PRIVATE HELPERS — single source of truth for slot access +# ================================================================================================= + +#! Loads token metadata word from storage. +#! +#! Inputs: [] +#! Outputs: [token_supply, max_supply, decimals, token_symbol] +#! +#! Where: +#! - token_supply, max_supply, decimals, token_symbol are the fields of the metadata word +#! (word[0] on top, little-endian). +#! +#! Invocation: exec +proc get_token_metadata_internal + push.TOKEN_METADATA_SLOT[0..2] + exec.active_account::get_item +end + +#! Loads name chunk 0 (1 Word). +#! +#! Inputs: [] +#! Outputs: [NAME_CHUNK_0] +#! +#! Invocation: exec +proc get_name_chunk_0 + push.NAME_CHUNK_0_SLOT[0..2] + exec.active_account::get_item +end + +#! Loads name chunk 1 (1 Word). +#! +#! Inputs: [] +#! Outputs: [NAME_CHUNK_1] +#! +#! Invocation: exec +proc get_name_chunk_1 + push.NAME_CHUNK_1_SLOT[0..2] + exec.active_account::get_item +end + +#! Loads the mutability config word. +#! +#! Inputs: [] +#! Outputs: [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable] +#! +#! Where: +#! - is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable are boolean flags +#! (word[0] on top after get_item, little-endian). +#! +#! Invocation: exec +proc get_mutability_config_word + push.MUTABILITY_CONFIG_SLOT[0..2] + exec.active_account::get_item +end + +#! Pipes 28 felts (7 words) from the advice stack into write_ptr and asserts the hash +#! matches COMMITMENT. adv.push_mapval must be called before this procedure to load +#! the data onto the advice stack. +#! +#! Inputs: [write_ptr, COMMITMENT] +#! Outputs: [] +#! +#! Invocation: exec +proc pipe_to_memory + push.7 + exec.mem::pipe_preimage_to_memory + # => [write_ptr'] + drop +end + +# ================================================================================================= +# PUBLIC API — TOKEN METADATA +# ================================================================================================= + +#! Returns the token metadata (token_supply, max_supply, decimals, token_symbol). +#! +#! Inputs: [pad(16)] +#! Outputs: [token_supply, max_supply, decimals, token_symbol, pad(12)] +#! +#! Invocation: call +pub proc get_token_metadata + exec.get_token_metadata_internal + swapw dropw + # => [token_supply, max_supply, decimals, token_symbol, pad(12)] +end + +#! Returns the maximum supply. +#! +#! Inputs: [pad(16)] +#! Outputs: [max_supply, pad(15)] +#! +#! Invocation: call +pub proc get_max_supply + exec.get_token_metadata_internal + # => [token_supply, max_supply, decimals, token_symbol, pad(16)] + swap movdn.4 dropw + # => [max_supply, pad(15)] +end + +#! Returns decimals (single felt; e.g. 8). +#! +#! Inputs: [pad(16)] +#! Outputs: [decimals, pad(15)] +#! +#! Invocation: call +pub proc get_decimals + exec.get_token_metadata_internal + # => [token_supply, max_supply, decimals, token_symbol, pad(16)] + movup.2 movdn.4 dropw + # => [decimals, pad(15)] +end + +#! Returns token_symbol (single felt). +#! +#! Inputs: [pad(16)] +#! Outputs: [token_symbol, pad(15)] +#! +#! Invocation: call +pub proc get_token_symbol + exec.get_token_metadata_internal + # => [token_supply, max_supply, decimals, token_symbol, pad(16)] + movup.3 movdn.4 dropw + # => [token_symbol, pad(15)] +end + +#! Returns token_supply (single felt). +#! +#! Inputs: [pad(16)] +#! Outputs: [token_supply, pad(15)] +#! +#! Invocation: call +pub proc get_token_supply + exec.get_token_metadata_internal + # => [token_supply, max_supply, decimals, token_symbol, pad(16)] + movdn.4 dropw + # => [token_supply, pad(15)] +end + +# ================================================================================================= +# NAME (2 words) +# ================================================================================================= + +#! Returns the token name. +#! +#! Inputs: [pad(16)] +#! Outputs: [NAME_CHUNK_0, NAME_CHUNK_1, pad(8)] +#! +#! Invocation: call +pub proc get_name + exec.get_name_chunk_1 + exec.get_name_chunk_0 + swapdw dropw dropw + # => [NAME_CHUNK_0, NAME_CHUNK_1, pad(8)] +end + +# ================================================================================================= +# MUTABILITY CONFIG — [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable] +# ================================================================================================= + +#! Returns the mutability config word. +#! +#! Inputs: [pad(16)] +#! Outputs: [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable, pad(12)] +#! +#! Invocation: call +pub proc get_mutability_config + exec.get_mutability_config_word + # => [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable, pad(16)] + swapw dropw + # => [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable, pad(12)] +end + +# ================================================================================================= +# MUTABILITY CHECKS — read mutability_config +# ================================================================================================= + +# Private helpers — load the mutability config word and extract a single flag. +# Used by both the public is_* procedures and the set_* procedures. + +#! Extracts the is_description_mutable flag from the mutability config word. +#! +#! Inputs: [] +#! Outputs: [is_description_mutable] +#! +#! Invocation: exec +proc is_description_mutable_inner + exec.get_mutability_config_word + # => [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable] + movdn.3 drop drop drop + # => [is_description_mutable] +end + +#! Extracts the is_logo_uri_mutable flag from the mutability config word. +#! +#! Inputs: [] +#! Outputs: [is_logo_uri_mutable] +#! +#! Invocation: exec +proc is_logo_uri_mutable_inner + exec.get_mutability_config_word + # => [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable] + drop movdn.2 drop drop + # => [is_logo_uri_mutable] +end + +#! Extracts the is_external_link_mutable flag from the mutability config word. +#! +#! Inputs: [] +#! Outputs: [is_external_link_mutable] +#! +#! Invocation: exec +proc is_external_link_mutable_inner + exec.get_mutability_config_word + # => [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable] + drop drop swap drop + # => [is_external_link_mutable] +end + +#! Extracts the is_max_supply_mutable flag from the mutability config word. +#! +#! Inputs: [] +#! Outputs: [is_max_supply_mutable] +#! +#! Invocation: exec +proc is_max_supply_mutable_inner + exec.get_mutability_config_word + # => [is_description_mutable, is_logo_uri_mutable, is_external_link_mutable, is_max_supply_mutable] + drop drop drop + # => [is_max_supply_mutable] +end + +#! Returns 1 if description is mutable, and 0 otherwise. +#! +#! Inputs: [pad(16)] +#! Outputs: [is_description_mutable, pad(15)] +#! +#! Invocation: call +pub proc is_description_mutable + exec.is_description_mutable_inner + # => [is_description_mutable, pad(16)] + swap drop + # => [is_description_mutable, pad(15)] +end + +#! Returns 1 if logo URI is mutable, and 0 otherwise. +#! +#! Inputs: [pad(16)] +#! Outputs: [is_logo_uri_mutable, pad(15)] +#! +#! Invocation: call +pub proc is_logo_uri_mutable + exec.is_logo_uri_mutable_inner + # => [is_logo_uri_mutable, pad(16)] + swap drop + # => [is_logo_uri_mutable, pad(15)] +end + +#! Returns 1 if external link is mutable, and 0 otherwise. +#! +#! Inputs: [pad(16)] +#! Outputs: [is_external_link_mutable, pad(15)] +#! +#! Invocation: call +pub proc is_external_link_mutable + exec.is_external_link_mutable_inner + # => [is_external_link_mutable, pad(16)] + swap drop + # => [is_external_link_mutable, pad(15)] +end + +#! Returns 1 if max supply is mutable, and 0 otherwise. +#! +#! Inputs: [pad(16)] +#! Outputs: [is_max_supply_mutable, pad(15)] +#! +#! Invocation: call +pub proc is_max_supply_mutable + exec.is_max_supply_mutable_inner + # => [is_max_supply_mutable, pad(16)] + swap drop + # => [is_max_supply_mutable, pad(15)] +end + +# ================================================================================================= +# SET DESCRIPTION (owner-only when is_description_mutable == 1) +# ================================================================================================= + +#! Updates the description (7 Words) if the description mutability flag is 1 +#! and the note sender is the owner. +#! +#! The caller passes the Poseidon hash of the new description on the stack and provides +#! the actual 7 Words in the advice map under that hash. The hash is verified against +#! the preimage during loading. +#! +#! Inputs: +#! Operand stack: [DESCRIPTION_HASH, pad(12)] +#! Advice map: { +#! DESCRIPTION_HASH: [description_elements], +#! } +#! Outputs: +#! Operand stack: [pad(16)] +#! +#! Where: +#! - description_elements are 28 felts (7 Words) encoding the description. +#! +#! Panics if: +#! - the description mutability flag is not 1. +#! - the note sender is not the owner. +#! - the preimage does not match DESCRIPTION_HASH. +#! +#! Invocation: call +@locals(28) +pub proc set_description + # Check mutability; verify owner. + # => [DESCRIPTION_HASH, pad(12)] + + exec.is_description_mutable_inner + # => [is_description_mutable, DESCRIPTION_HASH, pad(12)] + assert.err=ERR_DESCRIPTION_NOT_MUTABLE + # => [DESCRIPTION_HASH, pad(12)] + + exec.ownable2step::assert_sender_is_owner + # => [DESCRIPTION_HASH, pad(12)] + + # Pipe 7 words from advice map into local words 0–6, validating the hash. + adv.push_mapval + locaddr.0 + # => [locaddr.0, DESCRIPTION_HASH, pad(16)] + exec.pipe_to_memory + # => [pad(16)] + + loc_loadw_le.0 + push.DESCRIPTION_0_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.4 + push.DESCRIPTION_1_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.8 + push.DESCRIPTION_2_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.12 + push.DESCRIPTION_3_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.16 + push.DESCRIPTION_4_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.20 + push.DESCRIPTION_5_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.24 + push.DESCRIPTION_6_SLOT[0..2] + exec.native_account::set_item dropw +end + +# ================================================================================================= +# SET LOGO URI (owner-only when is_logo_uri_mutable == 1) +# ================================================================================================= + +#! Updates the logo URI (7 Words) if the logo URI mutability flag is 1 +#! and the note sender is the owner. +#! +#! The caller passes the Poseidon hash of the new logo URI on the stack and provides +#! the actual 7 Words in the advice map under that hash. The hash is verified against +#! the preimage during loading. +#! +#! Inputs: +#! Operand stack: [LOGO_URI_HASH, pad(12)] +#! Advice map: { +#! LOGO_URI_HASH: [logo_uri_elements], +#! } +#! Outputs: +#! Operand stack: [pad(16)] +#! +#! Where: +#! - logo_uri_elements are 28 felts (7 Words) encoding the logo URI. +#! +#! Panics if: +#! - the logo URI mutability flag is not 1. +#! - the note sender is not the owner. +#! - the preimage does not match LOGO_URI_HASH. +#! +#! Invocation: call +@locals(28) +pub proc set_logo_uri + # Check mutability; verify owner. + # => [LOGO_URI_HASH, pad(12)] + + exec.is_logo_uri_mutable_inner + # => [is_logo_uri_mutable, LOGO_URI_HASH, pad(12)] + assert.err=ERR_LOGO_URI_NOT_MUTABLE + # => [LOGO_URI_HASH, pad(12)] + + exec.ownable2step::assert_sender_is_owner + # => [LOGO_URI_HASH, pad(12)] + + adv.push_mapval + locaddr.0 + # => [locaddr.0, LOGO_URI_HASH, pad(16)] + exec.pipe_to_memory + # => [pad(16)] + + loc_loadw_le.0 + push.LOGO_URI_0_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.4 + push.LOGO_URI_1_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.8 + push.LOGO_URI_2_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.12 + push.LOGO_URI_3_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.16 + push.LOGO_URI_4_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.20 + push.LOGO_URI_5_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.24 + push.LOGO_URI_6_SLOT[0..2] + exec.native_account::set_item dropw +end + +# ================================================================================================= +# SET EXTERNAL LINK (owner-only when is_external_link_mutable == 1) +# ================================================================================================= + +#! Updates the external link (7 Words) if the external link mutability flag is 1 +#! and the note sender is the owner. +#! +#! The caller passes the Poseidon hash of the new external link on the stack and provides +#! the actual 7 Words in the advice map under that hash. The hash is verified against +#! the preimage during loading. +#! +#! Inputs: +#! Operand stack: [EXTERNAL_LINK_HASH, pad(12)] +#! Advice map: { +#! EXTERNAL_LINK_HASH: [external_link_elements], +#! } +#! Outputs: +#! Operand stack: [pad(16)] +#! +#! Where: +#! - external_link_elements are 28 felts (7 Words) encoding the external link. +#! +#! Panics if: +#! - the external link mutability flag is not 1. +#! - the note sender is not the owner. +#! - the preimage does not match EXTERNAL_LINK_HASH. +#! +#! Invocation: call +@locals(28) +pub proc set_external_link + # Check mutability; verify owner. + # => [EXTERNAL_LINK_HASH, pad(12)] + + exec.is_external_link_mutable_inner + # => [is_external_link_mutable, EXTERNAL_LINK_HASH, pad(12)] + assert.err=ERR_EXTERNAL_LINK_NOT_MUTABLE + # => [EXTERNAL_LINK_HASH, pad(12)] + + exec.ownable2step::assert_sender_is_owner + # => [EXTERNAL_LINK_HASH, pad(12)] + + adv.push_mapval + locaddr.0 + # => [locaddr.0, EXTERNAL_LINK_HASH, pad(16)] + exec.pipe_to_memory + # => [pad(16)] + + loc_loadw_le.0 + push.EXTERNAL_LINK_0_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.4 + push.EXTERNAL_LINK_1_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.8 + push.EXTERNAL_LINK_2_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.12 + push.EXTERNAL_LINK_3_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.16 + push.EXTERNAL_LINK_4_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.20 + push.EXTERNAL_LINK_5_SLOT[0..2] + exec.native_account::set_item dropw + + loc_loadw_le.24 + push.EXTERNAL_LINK_6_SLOT[0..2] + exec.native_account::set_item dropw +end + +# ================================================================================================= +# SET MAX SUPPLY (owner-only when is_max_supply_mutable == 1) +# ================================================================================================= + +#! Updates the max supply if the max supply mutability flag is 1 +#! and the note sender is the owner. +#! +#! Inputs: [new_max_supply, pad(15)] +#! Outputs: [pad(16)] +#! +#! Panics if: +#! - the max supply mutability flag is not 1. +#! - the note sender is not the owner. +#! - the new max supply is less than the current token supply. +#! +#! Invocation: call +pub proc set_max_supply + # 1. Check max supply mutability + exec.is_max_supply_mutable_inner + # => [is_max_supply_mutable, new_max_supply, pad(15)] + assert.err=ERR_MAX_SUPPLY_NOT_MUTABLE + + # 2. Verify note sender is the owner + exec.ownable2step::assert_sender_is_owner + # => [new_max_supply, pad(15)] + + # 3. Read current metadata word + exec.get_token_metadata_internal + # => [token_supply, max_supply, decimals, token_symbol, new_max_supply, pad(15)] + + # 4. Validate: token_supply <= new_max_supply + dup dup.5 + lte assert.err=ERR_NEW_MAX_SUPPLY_BELOW_TOKEN_SUPPLY + # => [token_supply, max_supply, decimals, token_symbol, new_max_supply, pad(15)] + + # 5. Replace max_supply (word[1]) with new_max_supply + movup.4 swap movup.2 drop + # => [token_supply, new_max_supply, decimals, token_symbol, pad(15)] + + # 6. Write updated metadata word back to storage + push.TOKEN_METADATA_SLOT[0..2] + exec.native_account::set_item + dropw + # => [pad(16)] +end diff --git a/crates/miden-standards/src/account/components/mod.rs b/crates/miden-standards/src/account/components/mod.rs index a14d3ce523..5c72956ce6 100644 --- a/crates/miden-standards/src/account/components/mod.rs +++ b/crates/miden-standards/src/account/components/mod.rs @@ -97,6 +97,15 @@ static NETWORK_FUNGIBLE_FAUCET_LIBRARY: LazyLock = LazyLock::new(|| { Library::read_from_bytes(bytes).expect("Shipped Network Fungible Faucet library is well-formed") }); +// Initialize the Fungible Token Metadata library only once. +static FUNGIBLE_TOKEN_METADATA_LIBRARY: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!( + env!("OUT_DIR"), + "/assets/account_components/faucets/fungible_token_metadata.masl" + )); + Library::read_from_bytes(bytes).expect("Shipped Fungible Token Metadata library is well-formed") +}); + // Initialize the Mint Policy Owner Controlled library only once. static MINT_POLICY_OWNER_CONTROLLED_LIBRARY: LazyLock = LazyLock::new(|| { let bytes = include_bytes!(concat!( @@ -117,9 +126,6 @@ static MINT_POLICY_AUTH_CONTROLLED_LIBRARY: LazyLock = LazyLock::new(|| .expect("Shipped Mint Policy Auth Controlled library is well-formed") }); -// METADATA LIBRARIES -// ================================================================================================ - /// Returns the Basic Wallet Library. pub fn basic_wallet_library() -> Library { BASIC_WALLET_LIBRARY.clone() @@ -140,6 +146,11 @@ pub fn network_fungible_faucet_library() -> Library { NETWORK_FUNGIBLE_FAUCET_LIBRARY.clone() } +/// Returns the Fungible Token Metadata Library. +pub fn fungible_token_metadata_library() -> Library { + FUNGIBLE_TOKEN_METADATA_LIBRARY.clone() +} + /// Returns the Mint Policy Owner Controlled Library. pub fn owner_controlled_library() -> Library { MINT_POLICY_OWNER_CONTROLLED_LIBRARY.clone() @@ -182,6 +193,7 @@ pub fn no_auth_library() -> Library { /// crate. pub enum StandardAccountComponent { BasicWallet, + FungibleTokenMetadata, BasicFungibleFaucet, NetworkFungibleFaucet, AuthSingleSig, @@ -196,6 +208,7 @@ impl StandardAccountComponent { pub fn procedure_digests(&self) -> impl Iterator { let library = match self { Self::BasicWallet => BASIC_WALLET_LIBRARY.as_ref(), + Self::FungibleTokenMetadata => FUNGIBLE_TOKEN_METADATA_LIBRARY.as_ref(), Self::BasicFungibleFaucet => BASIC_FUNGIBLE_FAUCET_LIBRARY.as_ref(), Self::NetworkFungibleFaucet => NETWORK_FUNGIBLE_FAUCET_LIBRARY.as_ref(), Self::AuthSingleSig => SINGLESIG_LIBRARY.as_ref(), @@ -239,6 +252,9 @@ impl StandardAccountComponent { Self::BasicWallet => { component_interface_vec.push(AccountComponentInterface::BasicWallet) }, + Self::FungibleTokenMetadata => { + component_interface_vec.push(AccountComponentInterface::FungibleTokenMetadata) + }, Self::BasicFungibleFaucet => { component_interface_vec.push(AccountComponentInterface::BasicFungibleFaucet) }, @@ -271,6 +287,7 @@ impl StandardAccountComponent { component_interface_vec: &mut Vec, ) { Self::BasicWallet.extract_component(procedures_set, component_interface_vec); + Self::FungibleTokenMetadata.extract_component(procedures_set, component_interface_vec); Self::BasicFungibleFaucet.extract_component(procedures_set, component_interface_vec); Self::NetworkFungibleFaucet.extract_component(procedures_set, component_interface_vec); Self::AuthSingleSig.extract_component(procedures_set, component_interface_vec); diff --git a/crates/miden-standards/src/account/faucets/basic_fungible.rs b/crates/miden-standards/src/account/faucets/basic_fungible.rs index 71b94ce2b6..444c71b621 100644 --- a/crates/miden-standards/src/account/faucets/basic_fungible.rs +++ b/crates/miden-standards/src/account/faucets/basic_fungible.rs @@ -1,10 +1,5 @@ -use miden_protocol::account::component::{ - AccountComponentMetadata, - FeltSchema, - SchemaType, - StorageSchema, - StorageSlotSchema, -}; +use miden_protocol::Word; +use miden_protocol::account::component::AccountComponentMetadata; use miden_protocol::account::{ Account, AccountBuilder, @@ -12,20 +7,15 @@ use miden_protocol::account::{ AccountStorage, AccountStorageMode, AccountType, - StorageSlotName, }; -use miden_protocol::asset::TokenSymbol; -use miden_protocol::{Felt, Word}; -use super::{FungibleFaucetError, TokenMetadata}; +use super::FungibleFaucetError; use crate::account::AuthMethod; use crate::account::auth::{AuthSingleSigAcl, AuthSingleSigAclConfig}; use crate::account::components::basic_fungible_faucet_library; -use crate::account::mint_policies::AuthControlled; - -/// The schema type for token symbols. -const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol"; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; +use crate::account::metadata::FungibleTokenMetadata; +use crate::account::mint_policies::AuthControlled; use crate::procedure_digest; // BASIC FUNGIBLE FAUCET ACCOUNT COMPONENT @@ -63,14 +53,11 @@ procedure_digest!( /// /// This component supports accounts of type [`AccountType::FungibleFaucet`]. /// -/// ## Storage Layout -/// -/// - [`Self::metadata_slot`]: Stores [`TokenMetadata`]. +/// This component depends on [`FungibleTokenMetadata`] being present in the account for storage +/// of token metadata. It has no storage slots of its own. /// /// [builder]: crate::code_builder::CodeBuilder -pub struct BasicFungibleFaucet { - metadata: TokenMetadata, -} +pub struct BasicFungibleFaucet; impl BasicFungibleFaucet { // CONSTANTS @@ -79,123 +66,12 @@ impl BasicFungibleFaucet { /// The name of the component. pub const NAME: &'static str = "miden::standards::components::faucets::basic_fungible_faucet"; - /// The maximum number of decimals supported by the component. - pub const MAX_DECIMALS: u8 = TokenMetadata::MAX_DECIMALS; - const MINT_PROC_NAME: &str = "mint_and_send"; const BURN_PROC_NAME: &str = "burn"; - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Creates a new [`BasicFungibleFaucet`] component from the given pieces of metadata and with - /// an initial token supply of zero. - /// - /// # Errors - /// - /// Returns an error if: - /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. - /// - the max supply parameter exceeds maximum possible amount for a fungible asset - /// ([`miden_protocol::asset::FungibleAsset::MAX_AMOUNT`]) - pub fn new( - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, - ) -> Result { - let metadata = TokenMetadata::new(symbol, decimals, max_supply)?; - Ok(Self { metadata }) - } - - /// Creates a new [`BasicFungibleFaucet`] component from the given [`TokenMetadata`]. - /// - /// This is a convenience constructor that allows creating a faucet from pre-validated - /// metadata. - pub fn from_metadata(metadata: TokenMetadata) -> Self { - Self { metadata } - } - - /// Attempts to create a new [`BasicFungibleFaucet`] component from the associated account - /// interface and storage. - /// - /// # Errors - /// - /// Returns an error if: - /// - the provided [`AccountInterface`] does not contain a - /// [`AccountComponentInterface::BasicFungibleFaucet`] component. - /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. - /// - the max supply value exceeds maximum possible amount for a fungible asset of - /// [`miden_protocol::asset::FungibleAsset::MAX_AMOUNT`]. - /// - the token supply exceeds the max supply. - /// - the token symbol encoded value exceeds the maximum value of - /// [`TokenSymbol::MAX_ENCODED_VALUE`]. - fn try_from_interface( - interface: AccountInterface, - storage: &AccountStorage, - ) -> Result { - // Check that the procedures of the basic fungible faucet exist in the account. - if !interface.components().contains(&AccountComponentInterface::BasicFungibleFaucet) { - return Err(FungibleFaucetError::MissingBasicFungibleFaucetInterface); - } - - let metadata = TokenMetadata::try_from(storage)?; - Ok(Self { metadata }) - } - // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the [`StorageSlotName`] where the [`BasicFungibleFaucet`]'s metadata is stored. - pub fn metadata_slot() -> &'static StorageSlotName { - TokenMetadata::metadata_slot() - } - - /// Returns the storage slot schema for the metadata slot. - pub fn metadata_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - let token_symbol_type = SchemaType::new(TOKEN_SYMBOL_TYPE).expect("valid type"); - ( - Self::metadata_slot().clone(), - StorageSlotSchema::value( - "Token metadata", - [ - FeltSchema::felt("token_supply").with_default(Felt::new(0)), - FeltSchema::felt("max_supply"), - FeltSchema::u8("decimals"), - FeltSchema::new_typed(token_symbol_type, "symbol"), - ], - ), - ) - } - - /// Returns the token metadata. - pub fn metadata(&self) -> &TokenMetadata { - &self.metadata - } - - /// Returns the symbol of the faucet. - pub fn symbol(&self) -> &TokenSymbol { - self.metadata.symbol() - } - - /// Returns the decimals of the faucet. - pub fn decimals(&self) -> u8 { - self.metadata.decimals() - } - - /// Returns the max supply (in base units) of the faucet. - /// - /// This is the highest amount of tokens that can be minted from this faucet. - pub fn max_supply(&self) -> Felt { - self.metadata.max_supply() - } - - /// Returns the token supply (in base units) of the faucet. - /// - /// This is the amount of tokens that were minted from the faucet so far. Its value can never - /// exceed [`Self::max_supply`]. - pub fn token_supply(&self) -> Felt { - self.metadata.token_supply() - } - /// Returns the digest of the `mint_and_send` account procedure. pub fn mint_and_send_digest() -> Word { *BASIC_FUNGIBLE_FAUCET_MINT_AND_SEND @@ -208,35 +84,28 @@ impl BasicFungibleFaucet { /// Returns the [`AccountComponentMetadata`] for this component. pub fn component_metadata() -> AccountComponentMetadata { - let storage_schema = StorageSchema::new([Self::metadata_slot_schema()]) - .expect("storage schema should be valid"); - AccountComponentMetadata::new(Self::NAME, [AccountType::FungibleFaucet]) .with_description("Basic fungible faucet component for minting and burning tokens") - .with_storage_schema(storage_schema) } - // MUTATORS - // -------------------------------------------------------------------------------------------- + /// Checks that the account contains the basic fungible faucet interface. + fn try_from_interface( + interface: AccountInterface, + _storage: &AccountStorage, + ) -> Result { + if !interface.components().contains(&AccountComponentInterface::BasicFungibleFaucet) { + return Err(FungibleFaucetError::MissingBasicFungibleFaucetInterface); + } - /// Sets the token_supply (in base units) of the basic fungible faucet. - /// - /// # Errors - /// - /// Returns an error if: - /// - the token supply exceeds the max supply. - pub fn with_token_supply(mut self, token_supply: Felt) -> Result { - self.metadata = self.metadata.with_token_supply(token_supply)?; - Ok(self) + Ok(BasicFungibleFaucet) } } impl From for AccountComponent { - fn from(faucet: BasicFungibleFaucet) -> Self { - let storage_slot = faucet.metadata.into(); + fn from(_faucet: BasicFungibleFaucet) -> Self { let metadata = BasicFungibleFaucet::component_metadata(); - AccountComponent::new(basic_fungible_faucet_library(), vec![storage_slot], metadata) + AccountComponent::new(basic_fungible_faucet_library(), vec![], metadata) .expect("basic fungible faucet component should satisfy the requirements of a valid account component") } } @@ -262,8 +131,7 @@ impl TryFrom<&Account> for BasicFungibleFaucet { } /// Creates a new faucet account with basic fungible faucet interface, -/// account storage type, specified authentication scheme, and provided meta data (token symbol, -/// decimals, max supply). +/// account storage type, specified authentication scheme, and provided metadata. /// /// The basic faucet interface exposes two procedures: /// - `mint_and_send`, which mints an assets and create a note for the provided recipient. @@ -275,14 +143,13 @@ impl TryFrom<&Account> for BasicFungibleFaucet { /// /// The storage layout of the faucet account is defined by the combination of the following /// components (see their docs for details): -/// - [`BasicFungibleFaucet`] +/// - [`FungibleTokenMetadata`] (token metadata, name, description, etc.) +/// - [`BasicFungibleFaucet`] (mint_and_send and burn procedures) /// - [`AuthSingleSigAcl`] /// - [`AuthControlled`] pub fn create_basic_fungible_faucet( init_seed: [u8; 32], - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, + metadata: FungibleTokenMetadata, account_storage_mode: AccountStorageMode, auth_method: AuthMethod, ) -> Result { @@ -320,7 +187,8 @@ pub fn create_basic_fungible_faucet( .account_type(AccountType::FungibleFaucet) .storage_mode(account_storage_mode) .with_auth_component(auth_component) - .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?) + .with_component(metadata) + .with_component(BasicFungibleFaucet) .with_component(AuthControlled::allow_all()) .build() .map_err(FungibleFaucetError::AccountError)?; @@ -334,8 +202,9 @@ pub fn create_basic_fungible_faucet( #[cfg(test)] mod tests { use assert_matches::assert_matches; - use miden_protocol::Word; use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment}; + use miden_protocol::asset::TokenSymbol; + use miden_protocol::{Felt, Word}; use super::{ AccountBuilder, @@ -343,12 +212,17 @@ mod tests { AccountType, AuthMethod, BasicFungibleFaucet, - Felt, FungibleFaucetError, - TokenSymbol, + FungibleTokenMetadata, create_basic_fungible_faucet, }; use crate::account::auth::{AuthSingleSig, AuthSingleSigAcl}; + use crate::account::metadata::{ + Description, + FungibleTokenMetadataBuilder, + TokenMetadata, + TokenName, + }; use crate::account::wallets::BasicWallet; #[test] @@ -364,22 +238,27 @@ mod tests { 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, ]; - let max_supply = Felt::new(123); + let max_supply = 123u64; let token_symbol_string = "POL"; let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap(); + let token_name_string = "polygon"; + let description_string = "A polygon token"; let decimals = 2u8; let storage_mode = AccountStorageMode::Private; - let token_symbol_felt = token_symbol.as_element(); - let faucet_account = create_basic_fungible_faucet( - init_seed, + let token_name = TokenName::new(token_name_string).unwrap(); + let description = Description::new(description_string).unwrap(); + let metadata = FungibleTokenMetadataBuilder::new( + token_name, token_symbol.clone(), decimals, max_supply, - storage_mode, - auth_method, ) + .description(description) + .build() .unwrap(); + let faucet_account = + create_basic_fungible_faucet(init_seed, metadata, storage_mode, auth_method).unwrap(); // The falcon auth component's public key should be present. assert_eq!( @@ -413,20 +292,31 @@ mod tests { // Check that faucet metadata was initialized to the given values. // Storage layout: [token_supply, max_supply, decimals, symbol] assert_eq!( - faucet_account.storage().get_item(BasicFungibleFaucet::metadata_slot()).unwrap(), - [Felt::ZERO, Felt::new(123), Felt::new(2), token_symbol_felt].into() + faucet_account + .storage() + .get_item(FungibleTokenMetadata::metadata_slot()) + .unwrap(), + [Felt::ZERO, Felt::new(123), Felt::new(2), token_symbol.into()].into() ); + // Check that name was stored + let name_0 = faucet_account.storage().get_item(TokenMetadata::name_chunk_0_slot()).unwrap(); + let name_1 = faucet_account.storage().get_item(TokenMetadata::name_chunk_1_slot()).unwrap(); + let decoded_name = TokenName::try_from_words(&[name_0, name_1]).unwrap(); + assert_eq!(decoded_name.as_str(), token_name_string); + let expected_desc_words = Description::new(description_string).unwrap().to_words(); + for (i, expected) in expected_desc_words.iter().enumerate() { + let chunk = + faucet_account.storage().get_item(TokenMetadata::description_slot(i)).unwrap(); + assert_eq!(chunk, *expected); + } + assert!(faucet_account.is_faucet()); assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet); - // Verify the faucet can be extracted and has correct metadata - let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap(); - assert_eq!(faucet_component.symbol(), &token_symbol); - assert_eq!(faucet_component.decimals(), decimals); - assert_eq!(faucet_component.max_supply(), max_supply); - assert_eq!(faucet_component.token_supply(), Felt::ZERO); + // Verify the faucet component can be extracted + let _faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap(); } #[test] @@ -438,12 +328,19 @@ mod tests { // valid account let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol"); + let metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("POL").unwrap(), + token_symbol, + 10, + 100u64, + ) + .build() + .expect("failed to create token metadata"); + let faucet_account = AccountBuilder::new(mock_seed) .account_type(AccountType::FungibleFaucet) - .with_component( - BasicFungibleFaucet::new(token_symbol.clone(), 10, Felt::new(100)) - .expect("failed to create a fungible faucet component"), - ) + .with_component(metadata) + .with_component(BasicFungibleFaucet) .with_auth_component(AuthSingleSig::new( mock_public_key, AuthScheme::Falcon512Poseidon2, @@ -451,17 +348,16 @@ mod tests { .build_existing() .expect("failed to create wallet account"); - let basic_ff = BasicFungibleFaucet::try_from(faucet_account) + let _basic_ff = BasicFungibleFaucet::try_from(faucet_account) .expect("basic fungible faucet creation failed"); - assert_eq!(basic_ff.symbol(), &token_symbol); - assert_eq!(basic_ff.decimals(), 10); - assert_eq!(basic_ff.max_supply(), Felt::new(100)); - assert_eq!(basic_ff.token_supply(), Felt::ZERO); // invalid account: basic fungible faucet component is missing let invalid_faucet_account = AccountBuilder::new(mock_seed) .account_type(AccountType::FungibleFaucet) - .with_auth_component(AuthSingleSig::new(mock_public_key, AuthScheme::Falcon512Poseidon2)) + .with_auth_component(AuthSingleSig::new( + mock_public_key, + AuthScheme::Falcon512Poseidon2, + )) // we need to add some other component so the builder doesn't fail .with_component(BasicWallet) .build_existing() diff --git a/crates/miden-standards/src/account/faucets/mod.rs b/crates/miden-standards/src/account/faucets/mod.rs index df1f3adc16..4151380c81 100644 --- a/crates/miden-standards/src/account/faucets/mod.rs +++ b/crates/miden-standards/src/account/faucets/mod.rs @@ -5,6 +5,7 @@ use miden_protocol::errors::{AccountError, TokenSymbolError}; use thiserror::Error; use crate::account::access::Ownable2StepError; +use crate::utils::FixedWidthStringError; mod basic_fungible; mod network_fungible; @@ -56,4 +57,12 @@ pub enum FungibleFaucetError { NotAFungibleFaucetAccount, #[error("failed to read ownership data from storage")] OwnershipError(#[source] Ownable2StepError), + #[error("mutability flag at index {index} has invalid value {value}: must be 0 or 1")] + InvalidMutabilityFlag { index: usize, value: u64 }, + #[error("invalid string data in field '{field}'")] + InvalidStringField { + field: &'static str, + #[source] + source: FixedWidthStringError, + }, } diff --git a/crates/miden-standards/src/account/faucets/network_fungible.rs b/crates/miden-standards/src/account/faucets/network_fungible.rs index bb7ba4e6be..b3b5cbb322 100644 --- a/crates/miden-standards/src/account/faucets/network_fungible.rs +++ b/crates/miden-standards/src/account/faucets/network_fungible.rs @@ -1,10 +1,5 @@ -use miden_protocol::account::component::{ - AccountComponentMetadata, - FeltSchema, - SchemaType, - StorageSchema, - StorageSlotSchema, -}; +use miden_protocol::Word; +use miden_protocol::account::component::AccountComponentMetadata; use miden_protocol::account::{ Account, AccountBuilder, @@ -12,22 +7,17 @@ use miden_protocol::account::{ AccountStorage, AccountStorageMode, AccountType, - StorageSlotName, }; -use miden_protocol::asset::TokenSymbol; -use miden_protocol::{Felt, Word}; -use super::{FungibleFaucetError, TokenMetadata}; +use super::FungibleFaucetError; use crate::account::access::AccessControl; use crate::account::auth::NoAuth; use crate::account::components::network_fungible_faucet_library; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; +use crate::account::metadata::FungibleTokenMetadata; use crate::account::mint_policies::OwnerControlled; use crate::procedure_digest; -/// The schema type for token symbols. -const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol"; - // NETWORK FUNGIBLE FAUCET ACCOUNT COMPONENT // ================================================================================================ @@ -64,14 +54,11 @@ procedure_digest!( /// `mint_and_send`. When building an account with this component, /// [`crate::account::access::Ownable2Step`] must also be included. /// -/// ## Storage Layout -/// -/// - [`Self::metadata_slot`]: Fungible faucet metadata. +/// This component depends on [`FungibleTokenMetadata`] being present in the account for storage +/// of token metadata. It has no storage slots of its own. /// /// [builder]: crate::code_builder::CodeBuilder -pub struct NetworkFungibleFaucet { - metadata: TokenMetadata, -} +pub struct NetworkFungibleFaucet; impl NetworkFungibleFaucet { // CONSTANTS @@ -80,57 +67,27 @@ impl NetworkFungibleFaucet { /// The name of the component. pub const NAME: &'static str = "miden::standards::components::faucets::network_fungible_faucet"; - /// The maximum number of decimals supported by the component. - pub const MAX_DECIMALS: u8 = TokenMetadata::MAX_DECIMALS; - const MINT_PROC_NAME: &str = "mint_and_send"; const BURN_PROC_NAME: &str = "burn"; - // CONSTRUCTORS + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Creates a new [`NetworkFungibleFaucet`] component from the given pieces of metadata. - /// - /// # Errors: - /// Returns an error if: - /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. - /// - the max supply parameter exceeds maximum possible amount for a fungible asset - /// ([`miden_protocol::asset::FungibleAsset::MAX_AMOUNT`]) - pub fn new( - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, - ) -> Result { - let metadata = TokenMetadata::new(symbol, decimals, max_supply)?; - Ok(Self { metadata }) + /// Returns the digest of the `mint_and_send` account procedure. + pub fn mint_and_send_digest() -> Word { + *NETWORK_FUNGIBLE_FAUCET_MINT_AND_SEND } - /// Creates a new [`NetworkFungibleFaucet`] component from the given [`TokenMetadata`]. - /// - /// This is a convenience constructor that allows creating a faucet from pre-validated - /// metadata. - pub fn from_metadata(metadata: TokenMetadata) -> Self { - Self { metadata } + /// Returns the digest of the `burn` account procedure. + pub fn burn_digest() -> Word { + *NETWORK_FUNGIBLE_FAUCET_BURN } - /// Attempts to create a new [`NetworkFungibleFaucet`] component from the associated account - /// interface and storage. - /// - /// # Errors: - /// Returns an error if: - /// - the provided [`AccountInterface`] does not contain a - /// [`AccountComponentInterface::NetworkFungibleFaucet`] component. - /// - the decimals parameter exceeds maximum value of [`Self::MAX_DECIMALS`]. - /// - the max supply value exceeds maximum possible amount for a fungible asset of - /// [`miden_protocol::asset::FungibleAsset::MAX_AMOUNT`]. - /// - the token supply exceeds the max supply. - /// - the token symbol encoded value exceeds the maximum value of - /// [`TokenSymbol::MAX_ENCODED_VALUE`]. + /// Checks that the account contains the network fungible faucet interface. fn try_from_interface( interface: AccountInterface, - storage: &AccountStorage, + _storage: &AccountStorage, ) -> Result { - // Check that the procedures of the network fungible faucet exist in the account. if !interface .components() .contains(&AccountComponentInterface::NetworkFungibleFaucet) @@ -138,113 +95,22 @@ impl NetworkFungibleFaucet { return Err(FungibleFaucetError::MissingNetworkFungibleFaucetInterface); } - // Read token metadata from storage - let metadata = TokenMetadata::try_from(storage)?; - - Ok(Self { metadata }) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the [`StorageSlotName`] where the [`NetworkFungibleFaucet`]'s metadata is stored. - pub fn metadata_slot() -> &'static StorageSlotName { - TokenMetadata::metadata_slot() - } - - /// Returns the storage slot schema for the metadata slot. - pub fn metadata_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - let token_symbol_type = SchemaType::new(TOKEN_SYMBOL_TYPE).expect("valid type"); - ( - Self::metadata_slot().clone(), - StorageSlotSchema::value( - "Token metadata", - [ - FeltSchema::felt("token_supply").with_default(Felt::new(0)), - FeltSchema::felt("max_supply"), - FeltSchema::u8("decimals"), - FeltSchema::new_typed(token_symbol_type, "symbol"), - ], - ), - ) - } - - /// Returns the token metadata. - pub fn metadata(&self) -> &TokenMetadata { - &self.metadata - } - - /// Returns the symbol of the faucet. - pub fn symbol(&self) -> &TokenSymbol { - self.metadata.symbol() - } - - /// Returns the decimals of the faucet. - pub fn decimals(&self) -> u8 { - self.metadata.decimals() - } - - /// Returns the max supply (in base units) of the faucet. - /// - /// This is the highest amount of tokens that can be minted from this faucet. - pub fn max_supply(&self) -> Felt { - self.metadata.max_supply() - } - - /// Returns the token supply (in base units) of the faucet. - /// - /// This is the amount of tokens that were minted from the faucet so far. Its value can never - /// exceed [`Self::max_supply`]. - pub fn token_supply(&self) -> Felt { - self.metadata.token_supply() - } - - /// Returns the digest of the `mint_and_send` account procedure. - pub fn mint_and_send_digest() -> Word { - *NETWORK_FUNGIBLE_FAUCET_MINT_AND_SEND - } - - /// Returns the digest of the `burn` account procedure. - pub fn burn_digest() -> Word { - *NETWORK_FUNGIBLE_FAUCET_BURN - } - - // MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Sets the token_supply (in base units) of the network fungible faucet. - /// - /// # Errors - /// - /// Returns an error if: - /// - the token supply exceeds the max supply. - pub fn with_token_supply(mut self, token_supply: Felt) -> Result { - self.metadata = self.metadata.with_token_supply(token_supply)?; - Ok(self) + Ok(NetworkFungibleFaucet) } /// Returns the [`AccountComponentMetadata`] for this component. pub fn component_metadata() -> AccountComponentMetadata { - let storage_schema = StorageSchema::new([Self::metadata_slot_schema()]) - .expect("storage schema should be valid"); - AccountComponentMetadata::new(Self::NAME, [AccountType::FungibleFaucet]) .with_description("Network fungible faucet component for minting and burning tokens") - .with_storage_schema(storage_schema) } } impl From for AccountComponent { - fn from(network_faucet: NetworkFungibleFaucet) -> Self { - let metadata_slot = network_faucet.metadata.into(); + fn from(_network_faucet: NetworkFungibleFaucet) -> Self { let metadata = NetworkFungibleFaucet::component_metadata(); - AccountComponent::new( - network_fungible_faucet_library(), - vec![metadata_slot], - metadata, - ) - .expect("network fungible faucet component should satisfy the requirements of a valid account component") + AccountComponent::new(network_fungible_faucet_library(), vec![], metadata) + .expect("network fungible faucet component should satisfy the requirements of a valid account component") } } @@ -269,7 +135,7 @@ impl TryFrom<&Account> for NetworkFungibleFaucet { } /// Creates a new faucet account with network fungible faucet interface and provided metadata -/// (token symbol, decimals, max supply) and access control. +/// and access control. /// /// The network faucet interface exposes two procedures: /// - `mint_and_send`, which mints an assets and create a note for the provided recipient. @@ -288,9 +154,7 @@ impl TryFrom<&Account> for NetworkFungibleFaucet { /// contains no additional storage slots for its auth ([`NoAuth`]). pub fn create_network_fungible_faucet( init_seed: [u8; 32], - symbol: TokenSymbol, - decimals: u8, - max_supply: Felt, + metadata: FungibleTokenMetadata, access_control: AccessControl, ) -> Result { // Validate that access_control is Ownable2Step, as this faucet depends on it. @@ -312,7 +176,8 @@ pub fn create_network_fungible_faucet( .account_type(AccountType::FungibleFaucet) .storage_mode(AccountStorageMode::Network) .with_auth_component(auth_component) - .with_component(NetworkFungibleFaucet::new(symbol, decimals, max_supply)?) + .with_component(metadata) + .with_component(NetworkFungibleFaucet) .with_component(access_control) .with_component(OwnerControlled::owner_only()) .build() @@ -327,16 +192,15 @@ pub fn create_network_fungible_faucet( #[cfg(test)] mod tests { use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; + use miden_protocol::asset::TokenSymbol; use super::*; use crate::account::access::Ownable2Step; + use crate::account::metadata::{FungibleTokenMetadataBuilder, TokenName}; #[test] fn test_create_network_fungible_faucet() { let init_seed = [7u8; 32]; - let symbol = TokenSymbol::new("NET").expect("token symbol should be valid"); - let decimals = 8u8; - let max_supply = Felt::new(1_000); let owner = AccountId::dummy( [1u8; 15], @@ -345,11 +209,18 @@ mod tests { AccountStorageMode::Private, ); + let metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("NET").expect("valid name"), + TokenSymbol::new("NET").expect("valid symbol"), + 8u8, + 1_000u64, + ) + .build() + .expect("valid metadata"); + let account = create_network_fungible_faucet( init_seed, - symbol.clone(), - decimals, - max_supply, + metadata, AccessControl::Ownable2Step { owner }, ) .expect("network faucet creation should succeed"); @@ -360,11 +231,7 @@ mod tests { expected_owner_word ); - let faucet = NetworkFungibleFaucet::try_from(&account) + let _faucet = NetworkFungibleFaucet::try_from(&account) .expect("network fungible faucet should be extractable from account"); - assert_eq!(faucet.symbol(), &symbol); - assert_eq!(faucet.decimals(), decimals); - assert_eq!(faucet.max_supply(), max_supply); - assert_eq!(faucet.token_supply(), Felt::ZERO); } } diff --git a/crates/miden-standards/src/account/faucets/token_metadata.rs b/crates/miden-standards/src/account/faucets/token_metadata.rs index bdca915fa5..d77dfdc3e3 100644 --- a/crates/miden-standards/src/account/faucets/token_metadata.rs +++ b/crates/miden-standards/src/account/faucets/token_metadata.rs @@ -1,3 +1,8 @@ +// TODO: This file exists solely to maintain backward compatibility with the AggLayer crate, which +// still depends on the old `TokenMetadata` type. It should be removed or replaced once the AggLayer +// is updated to use `FungibleTokenMetadata` or something else if agreed upon by the team. +// Note: The slot name was updated to match `FungibleTokenMetadata` and the MASM constant in +// `faucets/mod.masm`, because both types share the same `mint_and_send` procedure in MASM. use miden_protocol::account::{AccountStorage, StorageSlot, StorageSlotName}; use miden_protocol::asset::{FungibleAsset, TokenSymbol}; use miden_protocol::utils::sync::LazyLock; @@ -9,7 +14,7 @@ use super::FungibleFaucetError; // ================================================================================================ static METADATA_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::fungible_faucets::metadata") + StorageSlotName::new("miden::standards::metadata::fungible_faucet::token_metadata") .expect("storage slot name should be valid") }); diff --git a/crates/miden-standards/src/account/interface/component.rs b/crates/miden-standards/src/account/interface/component.rs index 6527767f3f..b8a1147669 100644 --- a/crates/miden-standards/src/account/interface/component.rs +++ b/crates/miden-standards/src/account/interface/component.rs @@ -19,6 +19,9 @@ pub enum AccountComponentInterface { /// Exposes procedures from the [`BasicWallet`][crate::account::wallets::BasicWallet] module. BasicWallet, /// Exposes procedures from the + /// [`FungibleTokenMetadata`][crate::account::metadata::FungibleTokenMetadata] module. + FungibleTokenMetadata, + /// Exposes procedures from the /// [`BasicFungibleFaucet`][crate::account::faucets::BasicFungibleFaucet] module. BasicFungibleFaucet, /// Exposes procedures from the @@ -57,6 +60,9 @@ impl AccountComponentInterface { pub fn name(&self) -> String { match self { AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(), + AccountComponentInterface::FungibleTokenMetadata => { + "Fungible Token Metadata".to_string() + }, AccountComponentInterface::BasicFungibleFaucet => "Basic Fungible Faucet".to_string(), AccountComponentInterface::NetworkFungibleFaucet => { "Network Fungible Faucet".to_string() diff --git a/crates/miden-standards/src/account/interface/extension.rs b/crates/miden-standards/src/account/interface/extension.rs index f23b1414a7..cf8b783080 100644 --- a/crates/miden-standards/src/account/interface/extension.rs +++ b/crates/miden-standards/src/account/interface/extension.rs @@ -13,6 +13,7 @@ use crate::account::components::{ StandardAccountComponent, basic_fungible_faucet_library, basic_wallet_library, + fungible_token_metadata_library, multisig_library, multisig_psm_library, network_fungible_faucet_library, @@ -92,6 +93,11 @@ impl AccountInterfaceExt for AccountInterface { component_proc_digests .extend(basic_wallet_library().mast_forest().procedure_digests()); }, + AccountComponentInterface::FungibleTokenMetadata => { + component_proc_digests.extend( + fungible_token_metadata_library().mast_forest().procedure_digests(), + ); + }, AccountComponentInterface::BasicFungibleFaucet => { component_proc_digests .extend(basic_fungible_faucet_library().mast_forest().procedure_digests()); diff --git a/crates/miden-standards/src/account/interface/test.rs b/crates/miden-standards/src/account/interface/test.rs index 5aef3f6203..223595579d 100644 --- a/crates/miden-standards/src/account/interface/test.rs +++ b/crates/miden-standards/src/account/interface/test.rs @@ -30,6 +30,7 @@ use crate::account::interface::{ AccountInterfaceExt, NoteAccountCompatibility, }; +use crate::account::metadata::{FungibleTokenMetadataBuilder, TokenName}; use crate::account::wallets::BasicWallet; use crate::code_builder::CodeBuilder; use crate::note::{P2idNote, P2ideNote, P2ideNoteStorage, SwapNote}; @@ -55,13 +56,16 @@ fn test_basic_wallet_default_notes() { .account_type(AccountType::FungibleFaucet) .with_auth_component(get_mock_falcon_auth_component()) .with_component( - BasicFungibleFaucet::new( + FungibleTokenMetadataBuilder::new( + TokenName::new("POL").unwrap(), TokenSymbol::new("POL").expect("invalid token symbol"), 10, - Felt::new(100), + 100u64, ) - .expect("failed to create a fungible faucet component"), + .build() + .expect("failed to create token metadata"), ) + .with_component(BasicFungibleFaucet) .build_existing() .expect("failed to create wallet account"); let faucet_account_interface = AccountInterface::from_account(&faucet_account); @@ -321,13 +325,16 @@ fn test_basic_fungible_faucet_custom_notes() { .account_type(AccountType::FungibleFaucet) .with_auth_component(get_mock_falcon_auth_component()) .with_component( - BasicFungibleFaucet::new( + FungibleTokenMetadataBuilder::new( + TokenName::new("POL").unwrap(), TokenSymbol::new("POL").expect("invalid token symbol"), 10, - Felt::new(100), + 100u64, ) - .expect("failed to create a fungible faucet component"), + .build() + .expect("failed to create token metadata"), ) + .with_component(BasicFungibleFaucet) .build_existing() .expect("failed to create wallet account"); let faucet_account_interface = AccountInterface::from_account(&faucet_account); diff --git a/crates/miden-standards/src/account/metadata/mod.rs b/crates/miden-standards/src/account/metadata/mod.rs index 00560c232b..c736190bc5 100644 --- a/crates/miden-standards/src/account/metadata/mod.rs +++ b/crates/miden-standards/src/account/metadata/mod.rs @@ -1,3 +1,12 @@ mod schema_commitment; +mod token_metadata; pub use schema_commitment::{AccountBuilderSchemaCommitmentExt, AccountSchemaCommitment}; +pub use token_metadata::fungible_token::{ + Description, + ExternalLink, + FungibleTokenMetadata, + FungibleTokenMetadataBuilder, + LogoURI, +}; +pub use token_metadata::{TokenMetadata, TokenName}; diff --git a/crates/miden-standards/src/account/metadata/schema_commitment.rs b/crates/miden-standards/src/account/metadata/schema_commitment.rs index b044609d22..1a0c7c7cd0 100644 --- a/crates/miden-standards/src/account/metadata/schema_commitment.rs +++ b/crates/miden-standards/src/account/metadata/schema_commitment.rs @@ -1,3 +1,11 @@ +//! Account storage schema commitment component. +//! +//! [`AccountSchemaCommitment`] computes a commitment over the merged storage schemas of all +//! account components and stores the result in a dedicated slot. The companion +//! [`AccountBuilderSchemaCommitmentExt`] trait adds a convenience method to +//! [`AccountBuilder`](miden_protocol::account::AccountBuilder) for building accounts with an +//! automatically computed schema commitment. + use alloc::collections::BTreeMap; use miden_protocol::Word; @@ -33,6 +41,7 @@ static STORAGE_SCHEMA_LIBRARY: LazyLock = LazyLock::new(|| { Library::read_from_bytes(bytes).expect("Shipped Storage Schema library is well-formed") }); +/// Schema commitment slot name. static SCHEMA_COMMITMENT_SLOT_NAME: LazyLock = LazyLock::new(|| { StorageSlotName::new("miden::standards::metadata::storage_schema::commitment") .expect("storage slot name should be valid") @@ -44,8 +53,8 @@ static SCHEMA_COMMITMENT_SLOT_NAME: LazyLock = LazyLock::new(|| /// An [`AccountComponent`] exposing the account storage schema commitment. /// /// The [`AccountSchemaCommitment`] component can be constructed from a list of [`StorageSchema`], -/// from which a commitment is computed and then inserted into the -/// `miden::standards::metadata::storage_schema::commitment` slot. +/// from which a commitment is computed and then inserted into the slot returned by +/// [`AccountSchemaCommitment::schema_commitment_slot()`]. /// /// It reexports the `get_schema_commitment` procedure from /// `miden::standards::metadata::storage_schema`. @@ -61,7 +70,6 @@ impl AccountSchemaCommitment { /// Name of the component is set to match the path of the corresponding module in the standards /// library. const NAME: &str = "miden::standards::metadata::storage_schema"; - /// Creates a new [`AccountSchemaCommitment`] component from storage schemas. /// /// The input schemas are merged into a single schema before the final commitment is computed. @@ -151,8 +159,8 @@ impl AccountBuilderSchemaCommitmentExt for AccountBuilder { /// Computes the schema commitment. /// -/// The account schema commitment is computed from the merged schema commitment. If the passed -/// list of schemas is empty, [`Word::empty()`] is returned. +/// The account schema commitment is computed from the merged schema commitment. +/// If the passed list of schemas is empty, [`Word::empty()`] is returned. fn compute_schema_commitment<'a>( schemas: impl IntoIterator, ) -> Result { diff --git a/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/builder.rs b/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/builder.rs new file mode 100644 index 0000000000..aa309b5f9f --- /dev/null +++ b/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/builder.rs @@ -0,0 +1,163 @@ +use miden_protocol::Felt; +use miden_protocol::asset::{FungibleAsset, TokenSymbol}; + +use super::super::{TokenMetadata, TokenName}; +use super::{Description, ExternalLink, FungibleTokenMetadata, LogoURI}; +use crate::account::faucets::FungibleFaucetError; + +/// Builder for [`FungibleTokenMetadata`] to avoid unwieldy optional arguments. +/// +/// Required fields are set in [`Self::new`]; optional fields and token supply +/// can be set via chainable methods. Token supply defaults to zero. +/// +/// # Example +/// +/// ``` +/// # use miden_protocol::Felt; +/// # use miden_protocol::asset::TokenSymbol; +/// # use miden_standards::account::metadata::{ +/// # Description, FungibleTokenMetadataBuilder, LogoURI, TokenName, +/// # }; +/// # fn main() -> Result<(), Box> { +/// let name = TokenName::new("My Token")?; +/// let symbol = TokenSymbol::new("MTK")?; +/// let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 8, 1_000_000) +/// .token_supply(Felt::from(100u32)) +/// .description(Description::new("A test token")?) +/// .logo_uri(LogoURI::new("https://example.com/logo.png")?) +/// .build()?; +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug, Clone)] +pub struct FungibleTokenMetadataBuilder { + name: TokenName, + symbol: TokenSymbol, + decimals: u8, + max_supply: u64, + token_supply: Felt, + description: Option, + logo_uri: Option, + external_link: Option, + is_description_mutable: bool, + is_logo_uri_mutable: bool, + is_external_link_mutable: bool, + is_max_supply_mutable: bool, +} + +impl FungibleTokenMetadataBuilder { + /// Creates a new builder with required fields. Token supply defaults to zero. + /// + /// # Parameters + /// + /// - `name`: display name (at most 32 UTF-8 bytes). + /// - `symbol`: token symbol. + /// - `decimals`: decimal precision; must be in the range `0..=12`. + /// - `max_supply`: maximum number of tokens that can ever be minted; must be in the range + /// `0..=FungibleAsset::MAX_AMOUNT` (≤ 2^63 − 1). Expressed as a `u64` rather than a [`Felt`] + /// to avoid accidental out-of-range values. + pub fn new(name: TokenName, symbol: TokenSymbol, decimals: u8, max_supply: u64) -> Self { + Self { + name, + symbol, + decimals, + max_supply, + token_supply: Felt::ZERO, + description: None, + logo_uri: None, + external_link: None, + is_description_mutable: false, + is_logo_uri_mutable: false, + is_external_link_mutable: false, + is_max_supply_mutable: false, + } + } + + /// Sets the initial token supply (default is zero). + pub fn token_supply(mut self, token_supply: Felt) -> Self { + self.token_supply = token_supply; + self + } + + /// Sets the optional description. + pub fn description(mut self, description: Description) -> Self { + self.description = Some(description); + self + } + + /// Sets the optional logo URI. + pub fn logo_uri(mut self, logo_uri: LogoURI) -> Self { + self.logo_uri = Some(logo_uri); + self + } + + /// Sets the optional external link. + pub fn external_link(mut self, external_link: ExternalLink) -> Self { + self.external_link = Some(external_link); + self + } + + /// Sets whether the description can be updated by the owner. + pub fn is_description_mutable(mut self, mutable: bool) -> Self { + self.is_description_mutable = mutable; + self + } + + /// Sets whether the logo URI can be updated by the owner. + pub fn is_logo_uri_mutable(mut self, mutable: bool) -> Self { + self.is_logo_uri_mutable = mutable; + self + } + + /// Sets whether the external link can be updated by the owner. + pub fn is_external_link_mutable(mut self, mutable: bool) -> Self { + self.is_external_link_mutable = mutable; + self + } + + /// Sets whether the max supply can be updated by the owner. + pub fn is_max_supply_mutable(mut self, mutable: bool) -> Self { + self.is_max_supply_mutable = mutable; + self + } + + /// Builds [`FungibleTokenMetadata`]. + pub fn build(self) -> Result { + if self.max_supply > FungibleAsset::MAX_AMOUNT { + return Err(FungibleFaucetError::MaxSupplyTooLarge { + actual: self.max_supply, + max: FungibleAsset::MAX_AMOUNT, + }); + } + // SAFETY: max_supply is validated above to be <= MAX_AMOUNT (2^63 - 1), + // which is well below the Goldilocks prime, so Felt::new will not wrap. + let max_supply = Felt::new(self.max_supply); + + let mut token_metadata = TokenMetadata::new(self.name); + if let Some(desc) = self.description { + token_metadata = token_metadata.with_description(desc, self.is_description_mutable); + } else { + token_metadata = token_metadata.with_description_mutable(self.is_description_mutable); + } + if let Some(uri) = self.logo_uri { + token_metadata = token_metadata.with_logo_uri(uri, self.is_logo_uri_mutable); + } else { + token_metadata = token_metadata.with_logo_uri_mutable(self.is_logo_uri_mutable); + } + if let Some(link) = self.external_link { + token_metadata = token_metadata.with_external_link(link, self.is_external_link_mutable); + } else { + token_metadata = + token_metadata.with_external_link_mutable(self.is_external_link_mutable); + } + token_metadata = token_metadata.with_max_supply_mutable(self.is_max_supply_mutable); + + FungibleTokenMetadata::new_validated( + self.symbol, + self.decimals, + max_supply, + self.token_supply, + token_metadata, + ) + } +} diff --git a/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/mod.rs b/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/mod.rs new file mode 100644 index 0000000000..ff8633e9c7 --- /dev/null +++ b/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/mod.rs @@ -0,0 +1,583 @@ +//! Fungible token metadata stored in account storage. +//! +//! ## Storage layout +//! +//! | Slot name | Contents | +//! |-----------|----------| +//! | `metadata::fungible_faucet::token_metadata` | `[token_supply, max_supply, decimals, token_symbol]` | +//! | `metadata::fungible_faucet::name_chunk_0` | first 4 felts of name | +//! | `metadata::fungible_faucet::name_chunk_1` | last 4 felts of name | +//! | `metadata::fungible_faucet::mutability_config` | `[is_desc_mutable, is_logo_mutable, is_extlink_mutable, is_max_supply_mutable]` | +//! | `metadata::fungible_faucet::description_0..=6` | description (7 Words, max 195 bytes) | +//! | `metadata::fungible_faucet::logo_uri_0..=6` | logo URI (7 Words, max 195 bytes) | +//! | `metadata::fungible_faucet::external_link_0..=6` | external link (7 Words, max 195 bytes) | +//! +//! Layout sync: the same layout is defined in MASM at +//! `asm/standards/metadata/fungible_faucet.masm`. Any change to slot names must be applied in both +//! Rust and MASM. +//! +//! ## Config Word +//! +//! `mutability_config`: `[is_desc_mutable, is_logo_mutable, is_extlink_mutable, +//! is_max_supply_mutable]` — each flag is 0 (immutable) or 1 (mutable / owner can update). +//! +//! Whether a field is *present* is determined by whether its storage words are all zero (absent) +//! or not (present). +//! +//! ## String encoding (UTF-8) +//! +//! All string fields use **7-bytes-per-felt, length-prefixed** encoding. The N felts are +//! serialized into a flat buffer of N × 7 bytes; byte 0 is the string length, followed by UTF-8 +//! content, zero-padded. Each 7-byte chunk is stored as a LE u64 with the high byte always zero, +//! so it always fits in a Goldilocks field element. +//! +//! The name slots hold 2 Words (8 felts, capacity 55 bytes, capped at 32). + +use alloc::vec::Vec; + +use miden_protocol::account::component::{ + AccountComponentMetadata, + FeltSchema, + SchemaType, + StorageSchema, + StorageSlotSchema, +}; +use miden_protocol::account::{ + AccountComponent, + AccountStorage, + AccountType, + StorageSlot, + StorageSlotName, +}; +use miden_protocol::asset::{FungibleAsset, TokenSymbol}; +use miden_protocol::utils::sync::LazyLock; +use miden_protocol::{Felt, Word}; + +use super::{TokenMetadata, TokenName}; +use crate::account::components::fungible_token_metadata_library; +use crate::account::faucets::FungibleFaucetError; +use crate::utils::{FixedWidthString, FixedWidthStringError}; + +pub mod builder; + +pub use builder::FungibleTokenMetadataBuilder; + +#[cfg(test)] +mod tests; + +// SLOT NAMES — canonical layout (sync with asm/standards/metadata/fungible_faucet.masm) +// ================================================================================================ + +/// Fungible token metadata word: `[token_supply, max_supply, decimals, token_symbol]`. +pub(crate) static FUNGIBLE_TOKEN_METADATA_SLOT: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::metadata::fungible_faucet::token_metadata") + .expect("storage slot name should be valid") +}); + +/// Token name (2 Words = 8 felts), split across 2 slots. +pub(crate) static NAME_SLOTS: LazyLock<[StorageSlotName; 2]> = LazyLock::new(|| { + [ + StorageSlotName::new("miden::standards::metadata::fungible_faucet::name_chunk_0") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::name_chunk_1") + .expect("valid slot name"), + ] +}); + +/// Mutability config slot: `[is_desc_mutable, is_logo_mutable, is_extlink_mutable, +/// is_max_supply_mutable]`. +pub(crate) static MUTABILITY_CONFIG_SLOT: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::metadata::fungible_faucet::mutability_config") + .expect("storage slot name should be valid") +}); + +/// Description (7 Words), split across 7 slots. +pub(crate) static DESCRIPTION_SLOTS: LazyLock<[StorageSlotName; 7]> = LazyLock::new(|| { + [ + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_0") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_1") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_2") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_3") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_4") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_5") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::description_6") + .expect("valid slot name"), + ] +}); + +/// Logo URI (7 Words), split across 7 slots. +pub(crate) static LOGO_URI_SLOTS: LazyLock<[StorageSlotName; 7]> = LazyLock::new(|| { + [ + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_0") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_1") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_2") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_3") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_4") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_5") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::logo_uri_6") + .expect("valid slot name"), + ] +}); + +/// External link (7 Words), split across 7 slots. +pub(crate) static EXTERNAL_LINK_SLOTS: LazyLock<[StorageSlotName; 7]> = LazyLock::new(|| { + [ + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_0") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_1") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_2") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_3") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_4") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_5") + .expect("valid slot name"), + StorageSlotName::new("miden::standards::metadata::fungible_faucet::external_link_6") + .expect("valid slot name"), + ] +}); + +/// Returns the [`StorageSlotName`] for the fungible token metadata word (slot 0). +pub(crate) fn fungible_token_metadata_slot() -> &'static StorageSlotName { + &FUNGIBLE_TOKEN_METADATA_SLOT +} + +/// Returns the [`StorageSlotName`] for the mutability config Word. +pub(crate) fn mutability_config_slot() -> &'static StorageSlotName { + &MUTABILITY_CONFIG_SLOT +} + +/// Schema type string for the token symbol field in fungible token metadata storage. +pub(super) const TOKEN_SYMBOL_TYPE: &str = + "miden::standards::fungible_faucets::metadata::token_symbol"; + +// FUNGIBLE TOKEN METADATA +// ================================================================================================ + +#[derive(Debug, Clone)] +pub struct FungibleTokenMetadata { + token_supply: Felt, + max_supply: Felt, + decimals: u8, + symbol: TokenSymbol, + /// Embeds name, optional fields, and mutability flags. + metadata: TokenMetadata, +} + +impl FungibleTokenMetadata { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The maximum number of decimals supported. + pub const MAX_DECIMALS: u8 = 12; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Returns a builder for [`FungibleTokenMetadata`] with the required fields set. + /// + /// This is the main entry point for constructing metadata; optional fields and token supply + /// can be set via the builder before calling [`FungibleTokenMetadataBuilder::build`]. + /// + /// # Parameters + /// + /// - `name`: display name (at most 32 UTF-8 bytes). + /// - `symbol`: token symbol. + /// - `decimals`: decimal precision (0–12). + /// - `max_supply`: maximum token supply (0–[`FungibleAsset::MAX_AMOUNT`], expressed as a + /// `u64`). + pub fn builder( + name: TokenName, + symbol: TokenSymbol, + decimals: u8, + max_supply: u64, + ) -> FungibleTokenMetadataBuilder { + FungibleTokenMetadataBuilder::new(name, symbol, decimals, max_supply) + } + + /// Validates all fields and constructs a [`FungibleTokenMetadata`]. + /// + /// This is the single point where `Self { ... }` is constructed. All other constructors + /// delegate here. + pub(crate) fn new_validated( + symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, + token_supply: Felt, + metadata: TokenMetadata, + ) -> Result { + if decimals > Self::MAX_DECIMALS { + return Err(FungibleFaucetError::TooManyDecimals { + actual: decimals as u64, + max: Self::MAX_DECIMALS, + }); + } + + if max_supply.as_canonical_u64() > FungibleAsset::MAX_AMOUNT { + return Err(FungibleFaucetError::MaxSupplyTooLarge { + actual: max_supply.as_canonical_u64(), + max: FungibleAsset::MAX_AMOUNT, + }); + } + + if token_supply.as_canonical_u64() > max_supply.as_canonical_u64() { + return Err(FungibleFaucetError::TokenSupplyExceedsMaxSupply { + token_supply: token_supply.as_canonical_u64(), + max_supply: max_supply.as_canonical_u64(), + }); + } + + Ok(Self { + token_supply, + max_supply, + decimals, + symbol, + metadata, + }) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the [`StorageSlotName`] where the token metadata is stored (canonical slot shared + /// with the metadata module). + pub fn metadata_slot() -> &'static StorageSlotName { + fungible_token_metadata_slot() + } + + /// Returns the current token supply (amount issued). + pub fn token_supply(&self) -> Felt { + self.token_supply + } + + /// Returns the maximum token supply. + pub fn max_supply(&self) -> Felt { + self.max_supply + } + + /// Returns the number of decimals. + pub fn decimals(&self) -> u8 { + self.decimals + } + + /// Returns the token symbol. + pub fn symbol(&self) -> &TokenSymbol { + &self.symbol + } + + /// Returns the token name. + pub fn name(&self) -> &TokenName { + self.metadata.name() + } + + /// Returns the optional description. + pub fn description(&self) -> Option<&Description> { + self.metadata.description() + } + + /// Returns the optional logo URI. + pub fn logo_uri(&self) -> Option<&LogoURI> { + self.metadata.logo_uri() + } + + /// Returns the optional external link. + pub fn external_link(&self) -> Option<&ExternalLink> { + self.metadata.external_link() + } + + /// Returns the storage slot schema for the metadata slot. + pub fn metadata_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + let token_symbol_type = SchemaType::new(TOKEN_SYMBOL_TYPE).expect("valid type"); + ( + Self::metadata_slot().clone(), + StorageSlotSchema::value( + "Token metadata", + [ + FeltSchema::felt("token_supply").with_default(Felt::ZERO), + FeltSchema::felt("max_supply"), + FeltSchema::u8("decimals"), + FeltSchema::new_typed(token_symbol_type, "symbol"), + ], + ), + ) + } + + /// Returns the single storage slot for the metadata word + /// `[token_supply, max_supply, decimals, symbol]`. Useful when only this slot is needed (e.g. + /// for components that extend the fungible metadata with additional slots). + fn metadata_word_slot(&self) -> StorageSlot { + let word = Word::new([ + self.token_supply, + self.max_supply, + Felt::from(self.decimals), + self.symbol.clone().into(), + ]); + StorageSlot::with_value(Self::metadata_slot().clone(), word) + } + + /// Consumes `self` and returns all storage slots for this component (metadata word + name + + /// config + description + logo_uri + external_link). + pub fn into_storage_slots(self) -> Vec { + let mut slots: Vec = Vec::new(); + slots.push(self.metadata_word_slot()); + slots.extend(self.metadata.into_storage_slots()); + slots + } + + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Sets the token_supply (in base units). + /// + /// # Errors + /// + /// Returns an error if: + /// - the token supply exceeds the max supply. + pub fn with_token_supply(mut self, token_supply: Felt) -> Result { + if token_supply.as_canonical_u64() > self.max_supply.as_canonical_u64() { + return Err(FungibleFaucetError::TokenSupplyExceedsMaxSupply { + token_supply: token_supply.as_canonical_u64(), + max_supply: self.max_supply.as_canonical_u64(), + }); + } + + self.token_supply = token_supply; + + Ok(self) + } + + /// Sets whether the description can be updated by the owner. + pub fn with_description_mutable(mut self, mutable: bool) -> Self { + self.metadata = self.metadata.with_description_mutable(mutable); + self + } + + /// Sets whether the logo URI can be updated by the owner. + pub fn with_logo_uri_mutable(mut self, mutable: bool) -> Self { + self.metadata = self.metadata.with_logo_uri_mutable(mutable); + self + } + + /// Sets whether the external link can be updated by the owner. + pub fn with_external_link_mutable(mut self, mutable: bool) -> Self { + self.metadata = self.metadata.with_external_link_mutable(mutable); + self + } + + /// Sets whether the max supply can be updated by the owner. + pub fn with_max_supply_mutable(mut self, mutable: bool) -> Self { + self.metadata = self.metadata.with_max_supply_mutable(mutable); + self + } +} + +// TRAIT IMPLEMENTATIONS +// ================================================================================================ + +impl FungibleTokenMetadata { + /// Reconstructs from the metadata word and the name/optionals/mutability read from storage. + pub(crate) fn from_metadata_word_and_token_metadata( + word: Word, + metadata: TokenMetadata, + ) -> Result { + let [token_supply, max_supply, decimals_felt, token_symbol] = *word; + let symbol = + TokenSymbol::try_from(token_symbol).map_err(FungibleFaucetError::InvalidTokenSymbol)?; + let decimals: u8 = decimals_felt.as_canonical_u64().try_into().map_err(|_| { + FungibleFaucetError::TooManyDecimals { + actual: decimals_felt.as_canonical_u64(), + max: Self::MAX_DECIMALS, + } + })?; + + Self::new_validated(symbol, decimals, max_supply, token_supply, metadata) + } +} + +impl From for AccountComponent { + fn from(metadata: FungibleTokenMetadata) -> Self { + let mut schema_entries = vec![FungibleTokenMetadata::metadata_slot_schema()]; + + // Name chunks (2 slots) + for (i, slot) in NAME_SLOTS.iter().enumerate() { + schema_entries.push(( + slot.clone(), + StorageSlotSchema::value( + alloc::format!("Name chunk {i}"), + core::array::from_fn(|j| FeltSchema::felt(alloc::format!("data_{j}"))), + ), + )); + } + + // Mutability config (1 slot) + schema_entries.push(( + MUTABILITY_CONFIG_SLOT.clone(), + StorageSlotSchema::value( + "Mutability config", + [ + FeltSchema::bool("is_description_mutable"), + FeltSchema::bool("is_logo_uri_mutable"), + FeltSchema::bool("is_external_link_mutable"), + FeltSchema::bool("is_max_supply_mutable"), + ], + ), + )); + + // Description, Logo URI, External link (7 slots each) + for (label, slots) in [ + ("Description", DESCRIPTION_SLOTS.as_slice()), + ("Logo URI", LOGO_URI_SLOTS.as_slice()), + ("External link", EXTERNAL_LINK_SLOTS.as_slice()), + ] { + for (i, slot) in slots.iter().enumerate() { + schema_entries.push(( + slot.clone(), + StorageSlotSchema::value( + alloc::format!("{label} chunk {i}"), + core::array::from_fn(|j| FeltSchema::felt(alloc::format!("data_{j}"))), + ), + )); + } + } + + let storage_schema = + StorageSchema::new(schema_entries).expect("storage schema should be valid"); + + let component_metadata = AccountComponentMetadata::new( + "miden::standards::components::faucets::fungible_token_metadata", + [AccountType::FungibleFaucet], + ) + .with_description("Fungible token metadata component storing token metadata, name, mutability config, description, logo URI, and external link") + .with_storage_schema(storage_schema); + + AccountComponent::new( + fungible_token_metadata_library(), + metadata.into_storage_slots(), + component_metadata, + ) + .expect("fungible token metadata component should satisfy the requirements of a valid account component") + } +} + +impl TryFrom<&AccountStorage> for FungibleTokenMetadata { + type Error = FungibleFaucetError; + + /// Reconstructs [`FungibleTokenMetadata`] by reading all relevant storage slots: the metadata + /// word, name, mutability config, description, logo URI, and external link. + fn try_from(storage: &AccountStorage) -> Result { + let metadata_word = storage.get_item(Self::metadata_slot()).map_err(|err| { + FungibleFaucetError::StorageLookupFailed { + slot_name: Self::metadata_slot().clone(), + source: err, + } + })?; + + let token_metadata = TokenMetadata::try_from_storage(storage)?; + + Self::from_metadata_word_and_token_metadata(metadata_word, token_metadata) + } +} + +// FIELD TYPES +// ================================================================================================ + +/// Token description (max 195 bytes UTF-8), stored in 7 Words. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Description(FixedWidthString<7>); + +impl Description { + /// Maximum byte length for a description (7 Words × 4 felts × 7 bytes − 1 length byte). + pub const MAX_BYTES: usize = FixedWidthString::<7>::CAPACITY; + + /// Creates a description from a UTF-8 string. + pub fn new(s: &str) -> Result { + FixedWidthString::<7>::new(s).map(Self) + } + + /// Returns the description as a string slice. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Encodes the description into 7 Words for storage. + pub fn to_words(&self) -> Vec { + self.0.to_words() + } + + /// Decodes a description from a 7-Word slice. + pub fn try_from_words(words: &[Word]) -> Result { + FixedWidthString::<7>::try_from_words(words).map(Self) + } +} + +/// Token logo URI (max 195 bytes UTF-8), stored in 7 Words. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LogoURI(FixedWidthString<7>); + +impl LogoURI { + /// Maximum byte length for a logo URI (7 Words × 4 felts × 7 bytes − 1 length byte). + pub const MAX_BYTES: usize = FixedWidthString::<7>::CAPACITY; + + /// Creates a logo URI from a UTF-8 string. + pub fn new(s: &str) -> Result { + FixedWidthString::<7>::new(s).map(Self) + } + + /// Returns the logo URI as a string slice. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Encodes the logo URI into 7 Words for storage. + pub fn to_words(&self) -> Vec { + self.0.to_words() + } + + /// Decodes a logo URI from a 7-Word slice. + pub fn try_from_words(words: &[Word]) -> Result { + FixedWidthString::<7>::try_from_words(words).map(Self) + } +} + +/// Token external link (max 195 bytes UTF-8), stored in 7 Words. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExternalLink(FixedWidthString<7>); + +impl ExternalLink { + /// Maximum byte length for an external link (7 Words × 4 felts × 7 bytes − 1 length byte). + pub const MAX_BYTES: usize = FixedWidthString::<7>::CAPACITY; + + /// Creates an external link from a UTF-8 string. + pub fn new(s: &str) -> Result { + FixedWidthString::<7>::new(s).map(Self) + } + + /// Returns the external link as a string slice. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Encodes the external link into 7 Words for storage. + pub fn to_words(&self) -> Vec { + self.0.to_words() + } + + /// Decodes an external link from a 7-Word slice. + pub fn try_from_words(words: &[Word]) -> Result { + FixedWidthString::<7>::try_from_words(words).map(Self) + } +} diff --git a/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/tests.rs b/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/tests.rs new file mode 100644 index 0000000000..5c574021d7 --- /dev/null +++ b/crates/miden-standards/src/account/metadata/token_metadata/fungible_token/tests.rs @@ -0,0 +1,527 @@ +use miden_protocol::asset::TokenSymbol; +use miden_protocol::{Felt, Word}; + +use super::{mutability_config_slot, *}; +use crate::account::metadata::{Description, ExternalLink, LogoURI}; + +#[test] +fn token_metadata_new() { + let symbol = TokenSymbol::new("TEST").unwrap(); + let decimals = 8u8; + let max_supply = 1_000_000u64; + let name = TokenName::new("TEST").unwrap(); + + let metadata = + FungibleTokenMetadataBuilder::new(name.clone(), symbol.clone(), decimals, max_supply) + .build() + .unwrap(); + + assert_eq!(metadata.symbol(), &symbol); + assert_eq!(metadata.decimals(), decimals); + assert_eq!(metadata.max_supply(), Felt::new(max_supply)); + assert_eq!(metadata.token_supply(), Felt::ZERO); + assert_eq!(metadata.name(), &name); + assert!(metadata.description().is_none()); + assert!(metadata.logo_uri().is_none()); + assert!(metadata.external_link().is_none()); +} + +#[test] +fn token_metadata_with_supply() { + let symbol = TokenSymbol::new("TEST").unwrap(); + let decimals = 8u8; + let max_supply = 1_000_000u64; + let token_supply = Felt::new(500_000); + let name = TokenName::new("TEST").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol.clone(), decimals, max_supply) + .token_supply(token_supply) + .build() + .unwrap(); + + assert_eq!(metadata.symbol(), &symbol); + assert_eq!(metadata.decimals(), decimals); + assert_eq!(metadata.max_supply(), Felt::new(max_supply)); + assert_eq!(metadata.token_supply(), token_supply); +} + +#[test] +fn token_metadata_builder_with_optionals() { + let symbol = TokenSymbol::new("MTK").unwrap(); + let name = TokenName::new("My Token").unwrap(); + let description = Description::new("A test token").unwrap(); + let logo_uri = LogoURI::new("https://example.com/logo.png").unwrap(); + let external_link = ExternalLink::new("https://example.com").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name.clone(), symbol.clone(), 8, 1_000_000u64) + .token_supply(Felt::new(100)) + .description(description.clone()) + .logo_uri(logo_uri.clone()) + .external_link(external_link.clone()) + .is_description_mutable(true) + .is_max_supply_mutable(true) + .build() + .unwrap(); + + assert_eq!(metadata.token_supply(), Felt::new(100)); + assert_eq!(metadata.description(), Some(&description)); + assert_eq!(metadata.logo_uri(), Some(&logo_uri)); + assert_eq!(metadata.external_link(), Some(&external_link)); + let slots = metadata.into_storage_slots(); + let config_word = slots[3].value(); + assert_eq!(config_word[0], Felt::from(1u32), "is_desc_mutable"); + assert_eq!(config_word[3], Felt::from(1u32), "is_max_supply_mutable"); +} + +#[test] +fn token_metadata_with_name_and_description() { + use miden_protocol::account::{AccountBuilder, AccountType}; + + use crate::account::auth::NoAuth; + use crate::account::faucets::BasicFungibleFaucet; + + let symbol = TokenSymbol::new("POL").unwrap(); + let decimals = 2u8; + let max_supply = 123u64; + let name = TokenName::new("polygon").unwrap(); + let description = Description::new("A polygon token").unwrap(); + + let metadata = + FungibleTokenMetadataBuilder::new(name.clone(), symbol.clone(), decimals, max_supply) + .description(description.clone()) + .build() + .unwrap(); + + assert_eq!(metadata.symbol(), &symbol); + assert_eq!(metadata.name(), &name); + assert_eq!(metadata.description(), Some(&description)); + + let account = AccountBuilder::new([2u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata.clone()) + .with_component(BasicFungibleFaucet) + .build() + .expect("account build should succeed"); + + let restored = FungibleTokenMetadata::try_from(account.storage()).unwrap(); + assert_eq!(restored.symbol(), &symbol); + assert_eq!(restored.name(), &name); + assert_eq!(restored.description(), Some(&description)); +} + +#[test] +fn token_name_roundtrip() { + let name = TokenName::new("polygon").unwrap(); + let words = name.to_words(); + let decoded = TokenName::try_from_words(&words).unwrap(); + assert_eq!(decoded.as_str(), "polygon"); +} + +#[test] +fn token_name_as_str() { + let name = TokenName::new("my_token").unwrap(); + assert_eq!(name.as_str(), "my_token"); +} + +#[test] +fn token_name_too_long() { + let s = "a".repeat(33); + assert!(TokenName::new(&s).is_err()); +} + +#[test] +fn description_roundtrip() { + let text = "A short description"; + let desc = Description::new(text).unwrap(); + let words = desc.to_words(); + let decoded = Description::try_from_words(&words).unwrap(); + assert_eq!(decoded.as_str(), text); +} + +#[test] +fn description_too_long() { + let s = "a".repeat(Description::MAX_BYTES + 1); + assert!(Description::new(&s).is_err()); +} + +#[test] +fn logo_uri_roundtrip() { + let url = "https://example.com/logo.png"; + let uri = LogoURI::new(url).unwrap(); + let words = uri.to_words(); + let decoded = LogoURI::try_from_words(&words).unwrap(); + assert_eq!(decoded.as_str(), url); +} + +#[test] +fn external_link_roundtrip() { + let url = "https://example.com"; + let link = ExternalLink::new(url).unwrap(); + let words = link.to_words(); + let decoded = ExternalLink::try_from_words(&words).unwrap(); + assert_eq!(decoded.as_str(), url); +} + +#[test] +fn token_metadata_too_many_decimals() { + let symbol = TokenSymbol::new("TEST").unwrap(); + let decimals = 13u8; + let max_supply = 1_000_000u64; + let name = TokenName::new("TEST").unwrap(); + + let result = FungibleTokenMetadataBuilder::new(name, symbol, decimals, max_supply).build(); + assert!(matches!(result, Err(FungibleFaucetError::TooManyDecimals { .. }))); +} + +#[test] +fn token_metadata_max_supply_too_large() { + use miden_protocol::asset::FungibleAsset; + + let symbol = TokenSymbol::new("TEST").unwrap(); + let decimals = 8u8; + let max_supply = FungibleAsset::MAX_AMOUNT + 1; + let name = TokenName::new("TEST").unwrap(); + + let result = FungibleTokenMetadataBuilder::new(name, symbol, decimals, max_supply).build(); + assert!(matches!(result, Err(FungibleFaucetError::MaxSupplyTooLarge { .. }))); +} + +#[test] +fn token_metadata_to_word() { + let symbol = TokenSymbol::new("POL").unwrap(); + let symbol_felt = symbol.as_element(); + let decimals = 2u8; + let max_supply = 123u64; + let name = TokenName::new("POL").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, decimals, max_supply) + .build() + .unwrap(); + let word = metadata.metadata_word_slot().value(); + + assert_eq!(word[0], Felt::ZERO); + assert_eq!(word[1], Felt::new(max_supply)); + assert_eq!(word[2], Felt::from(decimals)); + assert_eq!(word[3], symbol_felt); +} + +#[test] +fn token_metadata_from_account_storage() { + use miden_protocol::account::{AccountBuilder, AccountType}; + + use crate::account::auth::NoAuth; + use crate::account::faucets::BasicFungibleFaucet; + + let symbol = TokenSymbol::new("POL").unwrap(); + let decimals = 2u8; + let max_supply = 123u64; + let name = TokenName::new("POL").unwrap(); + + let original = FungibleTokenMetadataBuilder::new(name, symbol.clone(), decimals, max_supply) + .build() + .unwrap(); + + let account = AccountBuilder::new([3u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(original) + .with_component(BasicFungibleFaucet) + .build() + .expect("account build should succeed"); + + let restored = FungibleTokenMetadata::try_from(account.storage()).unwrap(); + + assert_eq!(restored.symbol(), &symbol); + assert_eq!(restored.decimals(), decimals); + assert_eq!(restored.max_supply(), Felt::new(max_supply)); + assert_eq!(restored.token_supply(), Felt::ZERO); +} + +#[test] +fn token_metadata_roundtrip_with_supply() { + use miden_protocol::account::{AccountBuilder, AccountType}; + + use crate::account::auth::NoAuth; + use crate::account::faucets::BasicFungibleFaucet; + + let symbol = TokenSymbol::new("POL").unwrap(); + let decimals = 2u8; + let max_supply = 1000u64; + let token_supply = Felt::new(500); + let name = TokenName::new("POL").unwrap(); + + let original = FungibleTokenMetadataBuilder::new(name, symbol.clone(), decimals, max_supply) + .token_supply(token_supply) + .build() + .unwrap(); + + let account = AccountBuilder::new([4u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(original) + .with_component(BasicFungibleFaucet) + .build() + .expect("account build should succeed"); + + let restored = FungibleTokenMetadata::try_from(account.storage()).unwrap(); + + assert_eq!(restored.symbol(), &symbol); + assert_eq!(restored.decimals(), decimals); + assert_eq!(restored.max_supply(), Felt::new(max_supply)); + assert_eq!(restored.token_supply(), token_supply); +} + +#[test] +fn mutability_builders() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("T").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 2, 1_000u64) + .is_description_mutable(true) + .is_logo_uri_mutable(true) + .is_external_link_mutable(false) + .is_max_supply_mutable(true) + .build() + .unwrap(); + + let slots = metadata.into_storage_slots(); + + // Slot layout (no owner slot): [0]=metadata, [1]=name_0, [2]=name_1, [3]=mutability_config + let config_slot = &slots[3]; + let config_word = config_slot.value(); + assert_eq!(config_word[0], Felt::from(1u32), "is_desc_mutable"); + assert_eq!(config_word[1], Felt::from(1u32), "is_logo_mutable"); + assert_eq!(config_word[2], Felt::from(0u32), "is_extlink_mutable"); + assert_eq!(config_word[3], Felt::from(1u32), "is_max_supply_mutable"); +} + +#[test] +fn mutability_defaults_to_false() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("T").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 2, 1_000u64).build().unwrap(); + + let slots = metadata.into_storage_slots(); + let config_word = slots[3].value(); + assert_eq!(config_word[0], Felt::ZERO, "is_desc_mutable default"); + assert_eq!(config_word[1], Felt::ZERO, "is_logo_mutable default"); + assert_eq!(config_word[2], Felt::ZERO, "is_extlink_mutable default"); + assert_eq!(config_word[3], Felt::ZERO, "is_max_supply_mutable default"); +} + +#[test] +fn storage_slots_includes_metadata_word() { + let symbol = TokenSymbol::new("POL").unwrap(); + let name = TokenName::new("polygon").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol.clone(), 2, 123u64) + .build() + .unwrap(); + let slots = metadata.into_storage_slots(); + + // First slot is the metadata word [token_supply, max_supply, decimals, symbol] + let metadata_word = slots[0].value(); + assert_eq!(metadata_word[0], Felt::ZERO); // token_supply + assert_eq!(metadata_word[1], Felt::new(123)); // max_supply + assert_eq!(metadata_word[2], Felt::from(2u32)); // decimals + assert_eq!(metadata_word[3], Felt::from(symbol)); // symbol +} + +#[test] +fn storage_slots_includes_name() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("my token").unwrap(); + let expected_words = name.to_words(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 2, 100u64).build().unwrap(); + let slots = metadata.into_storage_slots(); + + // Slot layout: [0]=metadata, [1]=name_0, [2]=name_1 + assert_eq!(slots[1].value(), expected_words[0]); + assert_eq!(slots[2].value(), expected_words[1]); +} + +#[test] +fn storage_slots_includes_description() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("T").unwrap(); + let description = Description::new("A cool token").unwrap(); + let expected_words = description.to_words(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 2, 100u64) + .description(description) + .build() + .unwrap(); + let slots = metadata.into_storage_slots(); + + // Slots 4..11 are description (7 words): after metadata(1) + name(2) + config(1) + for (i, expected) in expected_words.iter().enumerate() { + assert_eq!(slots[4 + i].value(), *expected, "description word {i}"); + } +} + +#[test] +fn storage_slots_total_count() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("T").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 2, 100u64).build().unwrap(); + let slots = metadata.into_storage_slots(); + + // 1 metadata + 2 name + 1 config + 7 description + 7 logo + 7 external_link = 25 + assert_eq!(slots.len(), 25); +} + +#[test] +fn into_account_component() { + use miden_protocol::account::{AccountBuilder, AccountType}; + + use crate::account::auth::NoAuth; + use crate::account::faucets::BasicFungibleFaucet; + + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("test token").unwrap(); + let description = Description::new("A test").unwrap(); + + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 4, 10_000u64) + .description(description) + .is_max_supply_mutable(true) + .build() + .unwrap(); + + // Should build an account successfully with FungibleTokenMetadata as a component + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build() + .expect("account build should succeed"); + + // Verify metadata slot is accessible + let md_word = account.storage().get_item(FungibleTokenMetadata::metadata_slot()).unwrap(); + assert_eq!(md_word[1], Felt::new(10_000)); // max_supply + assert_eq!(md_word[2], Felt::from(4u32)); // decimals + + // Verify mutability config + let config = account.storage().get_item(mutability_config_slot()).unwrap(); + assert_eq!(config[3], Felt::from(1u32), "is_max_supply_mutable"); +} + +#[test] +fn roundtrip_via_storage_matches_original() { + use miden_protocol::account::{AccountBuilder, AccountType}; + + use crate::account::auth::NoAuth; + use crate::account::faucets::BasicFungibleFaucet; + + let symbol = TokenSymbol::new("RND").unwrap(); + let name = TokenName::new("Roundtrip Token").unwrap(); + let description = Description::new("Description").unwrap(); + let logo_uri = LogoURI::new("https://example.com/logo.png").unwrap(); + let external_link = ExternalLink::new("https://example.com").unwrap(); + + let original = FungibleTokenMetadataBuilder::new(name.clone(), symbol.clone(), 6, 2_000_000u64) + .token_supply(Felt::new(100_000)) + .description(description.clone()) + .logo_uri(logo_uri.clone()) + .external_link(external_link.clone()) + .is_description_mutable(true) + .is_logo_uri_mutable(false) + .is_external_link_mutable(true) + .is_max_supply_mutable(false) + .build() + .unwrap(); + + let account = AccountBuilder::new([5u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(original) + .with_component(BasicFungibleFaucet) + .build() + .expect("account build should succeed"); + + let restored = FungibleTokenMetadata::try_from(account.storage()).unwrap(); + + assert_eq!(restored.symbol(), &symbol); + assert_eq!(restored.name(), &name); + assert_eq!(restored.decimals(), 6); + assert_eq!(restored.max_supply(), Felt::new(2_000_000)); + assert_eq!(restored.token_supply(), Felt::new(100_000)); + assert_eq!(restored.description(), Some(&description)); + assert_eq!(restored.logo_uri(), Some(&logo_uri)); + assert_eq!(restored.external_link(), Some(&external_link)); + let slots = restored.into_storage_slots(); + let config = slots[3].value(); + assert_eq!(config[0], Felt::from(1u32), "is_desc_mutable"); + assert_eq!(config[1], Felt::ZERO, "is_logo_mutable"); + assert_eq!(config[2], Felt::from(1u32), "is_extlink_mutable"); + assert_eq!(config[3], Felt::ZERO, "is_max_supply_mutable"); +} + +#[test] +fn logo_uri_too_long() { + let s = "a".repeat(LogoURI::MAX_BYTES + 1); + assert!(LogoURI::new(&s).is_err()); +} + +#[test] +fn external_link_too_long() { + let s = "a".repeat(ExternalLink::MAX_BYTES + 1); + assert!(ExternalLink::new(&s).is_err()); +} + +#[test] +fn name_max_32_bytes_accepted() { + let s = "a".repeat(TokenName::MAX_BYTES); + assert_eq!(s.len(), 32); + let name = TokenName::new(&s).unwrap(); + let words = name.to_words(); + let decoded = TokenName::try_from_words(&words).unwrap(); + assert_eq!(decoded.as_str(), s); +} + +#[test] +fn description_max_bytes_accepted() { + let s = "a".repeat(Description::MAX_BYTES); + let desc = Description::new(&s).unwrap(); + assert_eq!(desc.to_words().len(), 7); +} + +#[test] +fn token_supply_exceeds_max_supply() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("T").unwrap(); + let max_supply = 100u64; + let token_supply = Felt::new(101); + + let result = FungibleTokenMetadataBuilder::new(name, symbol, 2, max_supply) + .token_supply(token_supply) + .build(); + assert!(matches!(result, Err(FungibleFaucetError::TokenSupplyExceedsMaxSupply { .. }))); +} + +#[test] +fn with_token_supply_exceeds_max_supply() { + let symbol = TokenSymbol::new("TST").unwrap(); + let name = TokenName::new("T").unwrap(); + let metadata = FungibleTokenMetadataBuilder::new(name, symbol, 2, 100u64).build().unwrap(); + + let result = metadata.with_token_supply(Felt::new(101)); + assert!(matches!(result, Err(FungibleFaucetError::TokenSupplyExceedsMaxSupply { .. }))); +} + +#[test] +fn invalid_token_symbol_in_metadata_word() { + use super::super::TokenMetadata; + + // TokenSymbol::try_from(Felt) fails when the value exceeds MAX_ENCODED_VALUE. + let bad_symbol = Felt::new(TokenSymbol::MAX_ENCODED_VALUE + 1); + let bad_word = Word::from([Felt::ZERO, Felt::new(100), Felt::new(2), bad_symbol]); + let token_metadata = TokenMetadata::new(TokenName::new("test").unwrap()); + let result = + FungibleTokenMetadata::from_metadata_word_and_token_metadata(bad_word, token_metadata); + assert!(matches!(result, Err(FungibleFaucetError::InvalidTokenSymbol(_)))); +} diff --git a/crates/miden-standards/src/account/metadata/token_metadata/mod.rs b/crates/miden-standards/src/account/metadata/token_metadata/mod.rs new file mode 100644 index 0000000000..a9a3c11918 --- /dev/null +++ b/crates/miden-standards/src/account/metadata/token_metadata/mod.rs @@ -0,0 +1,407 @@ +//! Generic token metadata helper. +//! +//! [`TokenMetadata`] is a builder-pattern struct used to manage name and optional fields +//! (description, logo_uri, external_link) with their mutability flags in fixed value slots. +//! It is intended to be embedded inside [`fungible_token::FungibleTokenMetadata`] rather than used +//! as a standalone component. +//! +//! Ownership is handled by the `Ownable2Step` component. + +use alloc::vec::Vec; + +use miden_protocol::account::{AccountStorage, StorageSlot, StorageSlotName}; +use miden_protocol::{Felt, Word}; + +use crate::account::faucets::FungibleFaucetError; +use crate::utils::{FixedWidthString, FixedWidthStringError}; + +pub mod fungible_token; + +use fungible_token::{ + DESCRIPTION_SLOTS, + Description, + EXTERNAL_LINK_SLOTS, + ExternalLink, + LOGO_URI_SLOTS, + LogoURI, + NAME_SLOTS, + mutability_config_slot, +}; + +/// Maximum length of a name in bytes when using the UTF-8 encoding (capped at 32). +pub(crate) const NAME_UTF8_MAX_BYTES: usize = 32; + +// TOKEN NAME +// ================================================================================================ + +/// Token display name (max 32 bytes UTF-8), stored in 2 Words. +/// +/// The maximum is intentionally capped at 32 bytes even though the 2-Word encoding could +/// hold up to 55 bytes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TokenName(FixedWidthString<2>); + +impl TokenName { + /// Maximum byte length for a token name (capped at 32, below the 55-byte capacity). + pub const MAX_BYTES: usize = NAME_UTF8_MAX_BYTES; + + /// Creates a token name from a UTF-8 string (at most 32 bytes). + pub fn new(s: &str) -> Result { + if s.len() > Self::MAX_BYTES { + return Err(FixedWidthStringError::TooLong { max: Self::MAX_BYTES, actual: s.len() }); + } + Ok(Self(FixedWidthString::new(s).expect("length already validated above"))) + } + + /// Returns the name as a string slice. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Encodes the name into 2 Words for storage. + pub fn to_words(&self) -> Vec { + self.0.to_words() + } + + /// Decodes a token name from a 2-Word slice. + pub fn try_from_words(words: &[Word]) -> Result { + let inner = FixedWidthString::<2>::try_from_words(words)?; + if inner.as_str().len() > Self::MAX_BYTES { + return Err(FixedWidthStringError::TooLong { + max: Self::MAX_BYTES, + actual: inner.as_str().len(), + }); + } + Ok(Self(inner)) + } +} + +// TOKEN METADATA +// ================================================================================================ + +/// A helper that stores name, mutability config, and optional fields in fixed value slots. +/// +/// Designed to be embedded in [`FungibleTokenMetadata`] to avoid duplication. Slot names are +/// defined in the `fungible_token` module and referenced via [`TokenMetadata::name_chunk_0_slot`]. +/// +/// ## Storage Layout +/// +/// - Slot 0–1: name (2 Words = 8 felts) +/// - Slot 2: mutability_config `[desc_mutable, logo_mutable, extlink_mutable, +/// is_max_supply_mutable]` +/// - Slot 3–9: description (7 Words) +/// - Slot 10–16: logo_uri (7 Words) +/// - Slot 17–23: external_link (7 Words) +/// +/// [`FungibleTokenMetadata`]: crate::account::metadata::FungibleTokenMetadata +/// [`name_chunk_0_slot`]: TokenMetadata::name_chunk_0_slot +#[derive(Debug, Clone)] +pub struct TokenMetadata { + name: TokenName, + description: Option, + logo_uri: Option, + external_link: Option, + is_description_mutable: bool, + is_logo_uri_mutable: bool, + is_external_link_mutable: bool, + is_max_supply_mutable: bool, +} + +impl TokenMetadata { + /// Creates a new token metadata with the given name (all optional fields absent, all flags + /// false). + pub fn new(name: TokenName) -> Self { + Self { + name, + description: None, + logo_uri: None, + external_link: None, + is_description_mutable: false, + is_logo_uri_mutable: false, + is_external_link_mutable: false, + is_max_supply_mutable: false, + } + } + + // BUILDERS + // -------------------------------------------------------------------------------------------- + + /// Sets the description and its mutability flag together. + pub fn with_description(mut self, description: Description, mutable: bool) -> Self { + self.description = Some(description); + self.is_description_mutable = mutable; + self + } + + /// Sets whether the description can be updated by the owner. + pub fn with_description_mutable(mut self, mutable: bool) -> Self { + self.is_description_mutable = mutable; + self + } + + /// Sets the logo URI and its mutability flag together. + pub fn with_logo_uri(mut self, logo_uri: LogoURI, mutable: bool) -> Self { + self.logo_uri = Some(logo_uri); + self.is_logo_uri_mutable = mutable; + self + } + + /// Sets whether the logo URI can be updated by the owner. + pub fn with_logo_uri_mutable(mut self, mutable: bool) -> Self { + self.is_logo_uri_mutable = mutable; + self + } + + /// Sets the external link and its mutability flag together. + pub fn with_external_link(mut self, external_link: ExternalLink, mutable: bool) -> Self { + self.external_link = Some(external_link); + self.is_external_link_mutable = mutable; + self + } + + /// Sets whether the external link can be updated by the owner. + pub fn with_external_link_mutable(mut self, mutable: bool) -> Self { + self.is_external_link_mutable = mutable; + self + } + + /// Sets whether the max supply can be updated by the owner. + pub fn with_max_supply_mutable(mut self, mutable: bool) -> Self { + self.is_max_supply_mutable = mutable; + self + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the token name. + pub fn name(&self) -> &TokenName { + &self.name + } + + /// Returns the description if set. + pub fn description(&self) -> Option<&Description> { + self.description.as_ref() + } + + /// Returns the logo URI if set. + pub fn logo_uri(&self) -> Option<&LogoURI> { + self.logo_uri.as_ref() + } + + /// Returns the external link if set. + pub fn external_link(&self) -> Option<&ExternalLink> { + self.external_link.as_ref() + } + + // STATIC SLOT NAME ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the [`StorageSlotName`] for name chunk 0. + pub fn name_chunk_0_slot() -> &'static StorageSlotName { + &NAME_SLOTS[0] + } + + /// Returns the [`StorageSlotName`] for name chunk 1. + pub fn name_chunk_1_slot() -> &'static StorageSlotName { + &NAME_SLOTS[1] + } + + /// Returns the [`StorageSlotName`] for a description chunk by index (0..=6). + pub fn description_slot(index: usize) -> &'static StorageSlotName { + &DESCRIPTION_SLOTS[index] + } + + /// Returns the [`StorageSlotName`] for a logo URI chunk by index (0..=6). + pub fn logo_uri_slot(index: usize) -> &'static StorageSlotName { + &LOGO_URI_SLOTS[index] + } + + /// Returns the [`StorageSlotName`] for an external link chunk by index (0..=6). + pub fn external_link_slot(index: usize) -> &'static StorageSlotName { + &EXTERNAL_LINK_SLOTS[index] + } + + // STORAGE + // -------------------------------------------------------------------------------------------- + + /// Converts a single [`Felt`] at the given `index` in the mutability config word to a `bool`. + /// + /// Returns `Err` if the value is neither `0` nor `1`. + fn felt_to_bool(felt: Felt, index: usize) -> Result { + match felt.as_canonical_u64() { + 0 => Ok(false), + 1 => Ok(true), + value => Err(FungibleFaucetError::InvalidMutabilityFlag { index, value }), + } + } + + /// Decodes the mutability config [`Word`] into its four boolean flags. + /// + /// The word layout is `[is_desc_mutable, is_logo_mutable, is_extlink_mutable, + /// is_max_supply_mutable]`. Each element must be exactly `0` or `1`. + /// + /// # Errors + /// + /// Returns [`FungibleFaucetError::InvalidMutabilityFlag`] if any element is not `0` or `1`. + fn mutability_flags_from_word( + word: Word, + ) -> Result<(bool, bool, bool, bool), FungibleFaucetError> { + Ok(( + Self::felt_to_bool(word[0], 0)?, + Self::felt_to_bool(word[1], 1)?, + Self::felt_to_bool(word[2], 2)?, + Self::felt_to_bool(word[3], 3)?, + )) + } + + /// Returns the mutability config word for this metadata. + fn mutability_config_word(&self) -> Word { + Word::from([ + Felt::from(self.is_description_mutable as u32), + Felt::from(self.is_logo_uri_mutable as u32), + Felt::from(self.is_external_link_mutable as u32), + Felt::from(self.is_max_supply_mutable as u32), + ]) + } + + /// Constructs a [`TokenMetadata`] by reading all relevant name, optional-field, and + /// mutability config slots from account storage. + /// + /// # Errors + /// + /// Returns [`FungibleFaucetError`] if any storage lookup fails, a mutability flag is invalid, + /// or a string field cannot be decoded. + pub fn try_from_storage(storage: &AccountStorage) -> Result { + let chunk_0 = storage.get_item(TokenMetadata::name_chunk_0_slot()).map_err(|err| { + FungibleFaucetError::StorageLookupFailed { + slot_name: TokenMetadata::name_chunk_0_slot().clone(), + source: err, + } + })?; + let chunk_1 = storage.get_item(TokenMetadata::name_chunk_1_slot()).map_err(|err| { + FungibleFaucetError::StorageLookupFailed { + slot_name: TokenMetadata::name_chunk_1_slot().clone(), + source: err, + } + })?; + let name_words: [Word; 2] = [chunk_0, chunk_1]; + let name = TokenName::try_from_words(&name_words).map_err(|err| { + FungibleFaucetError::InvalidStringField { field: "name", source: err } + })?; + + let read_slots = |slots: &[StorageSlotName; 7]| -> Result<[Word; 7], FungibleFaucetError> { + let mut field = [Word::default(); 7]; + for (i, slot) in slots.iter().enumerate() { + field[i] = storage.get_item(slot).map_err(|err| { + FungibleFaucetError::StorageLookupFailed { + slot_name: slot.clone(), + source: err, + } + })?; + } + Ok(field) + }; + + let description_words = read_slots(&DESCRIPTION_SLOTS)?; + let description = Description::try_from_words(&description_words).map_err(|err| { + FungibleFaucetError::InvalidStringField { field: "description", source: err } + })?; + let description = if description.as_str().is_empty() { + None + } else { + Some(description) + }; + + let logo_words = read_slots(&LOGO_URI_SLOTS)?; + let logo_uri = LogoURI::try_from_words(&logo_words).map_err(|err| { + FungibleFaucetError::InvalidStringField { field: "logo_uri", source: err } + })?; + let logo_uri = if logo_uri.as_str().is_empty() { + None + } else { + Some(logo_uri) + }; + + let link_words = read_slots(&EXTERNAL_LINK_SLOTS)?; + let external_link = ExternalLink::try_from_words(&link_words).map_err(|err| { + FungibleFaucetError::InvalidStringField { field: "external_link", source: err } + })?; + let external_link = if external_link.as_str().is_empty() { + None + } else { + Some(external_link) + }; + + let mutability_word = storage.get_item(mutability_config_slot()).map_err(|err| { + FungibleFaucetError::StorageLookupFailed { + slot_name: mutability_config_slot().clone(), + source: err, + } + })?; + let (is_desc_mutable, is_logo_mutable, is_extlink_mutable, is_max_supply_mutable) = + TokenMetadata::mutability_flags_from_word(mutability_word)?; + + let mut meta = TokenMetadata::new(name); + if let Some(d) = description { + meta = meta.with_description(d, is_desc_mutable); + } + meta = meta.with_description_mutable(is_desc_mutable); + if let Some(l) = logo_uri { + meta = meta.with_logo_uri(l, is_logo_mutable); + } + meta = meta.with_logo_uri_mutable(is_logo_mutable); + if let Some(e) = external_link { + meta = meta.with_external_link(e, is_extlink_mutable); + } + meta = meta.with_external_link_mutable(is_extlink_mutable); + meta = meta.with_max_supply_mutable(is_max_supply_mutable); + + Ok(meta) + } + + /// Consumes `self` and returns the storage slots for this metadata (name, mutability config, + /// and all fields). Absent optional fields are encoded as empty strings (all-zero words). + pub fn into_storage_slots(self) -> Vec { + let mut slots: Vec = Vec::new(); + + let name_words = self.name.to_words(); + slots.push(StorageSlot::with_value( + TokenMetadata::name_chunk_0_slot().clone(), + name_words[0], + )); + slots.push(StorageSlot::with_value( + TokenMetadata::name_chunk_1_slot().clone(), + name_words[1], + )); + + slots.push(StorageSlot::with_value( + mutability_config_slot().clone(), + self.mutability_config_word(), + )); + + let description = self + .description + .unwrap_or_else(|| Description::new("").expect("empty description should be valid")); + for (i, word) in description.to_words().iter().enumerate() { + slots.push(StorageSlot::with_value(TokenMetadata::description_slot(i).clone(), *word)); + } + + let logo_uri = self + .logo_uri + .unwrap_or_else(|| LogoURI::new("").expect("empty logo URI should be valid")); + for (i, word) in logo_uri.to_words().iter().enumerate() { + slots.push(StorageSlot::with_value(TokenMetadata::logo_uri_slot(i).clone(), *word)); + } + + let external_link = self + .external_link + .unwrap_or_else(|| ExternalLink::new("").expect("empty external link should be valid")); + for (i, word) in external_link.to_words().iter().enumerate() { + slots + .push(StorageSlot::with_value(TokenMetadata::external_link_slot(i).clone(), *word)); + } + + slots + } +} diff --git a/crates/miden-standards/src/errors/mod.rs b/crates/miden-standards/src/errors/mod.rs index f1c21dd45b..8563932cd8 100644 --- a/crates/miden-standards/src/errors/mod.rs +++ b/crates/miden-standards/src/errors/mod.rs @@ -5,4 +5,5 @@ pub mod standards { } mod code_builder_errors; + pub use code_builder_errors::CodeBuilderError; diff --git a/crates/miden-standards/src/utils/mod.rs b/crates/miden-standards/src/utils/mod.rs index d245b85214..c4a10cd7f4 100644 --- a/crates/miden-standards/src/utils/mod.rs +++ b/crates/miden-standards/src/utils/mod.rs @@ -1 +1,2 @@ -pub mod string; +mod string; +pub use string::{FixedWidthString, FixedWidthStringError}; diff --git a/crates/miden-standards/src/utils/string.rs b/crates/miden-standards/src/utils/string.rs index 5716961846..7307efb620 100644 --- a/crates/miden-standards/src/utils/string.rs +++ b/crates/miden-standards/src/utils/string.rs @@ -320,4 +320,27 @@ mod tests { let s: FixedWidthString<2> = FixedWidthString::default(); assert_eq!(s.as_str(), ""); } + + #[test] + fn empty_string_encodes_to_7_empty_words() { + // An empty FixedWidthString encodes to all-zero words because the length prefix is 0 + // and the rest of the buffer is zero-padded. This property is relied upon by + // `TokenMetadata::storage_slots` to encode absent optional fields as empty word slices. + let s = FixedWidthString::<7>::new("").unwrap(); + let words = s.to_words(); + assert_eq!(words.len(), 7); + for word in &words { + assert_eq!(*word, Word::default()); + } + } + + #[test] + fn empty_string_encodes_to_9_empty_words() { + let s = FixedWidthString::<9>::new("").unwrap(); + let words = s.to_words(); + assert_eq!(words.len(), 9); + for word in &words { + assert_eq!(*word, Word::default()); + } + } } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account.rs b/crates/miden-testing/src/kernel_tests/tx/test_account.rs index 83cf5aaed0..d2aa1c4475 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account.rs @@ -55,6 +55,7 @@ use miden_protocol::testing::storage::{MOCK_MAP_SLOT, MOCK_VALUE_SLOT0, MOCK_VAL use miden_protocol::transaction::{RawOutputNote, TransactionKernel}; use miden_protocol::utils::sync::LazyLock; use miden_standards::account::faucets::BasicFungibleFaucet; +use miden_standards::account::metadata::{FungibleTokenMetadataBuilder, TokenName}; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::account_component::MockAccountComponent; use miden_standards::testing::mock_account::MockAccountExt; @@ -1700,12 +1701,19 @@ async fn test_faucet_has_callbacks( #[case] callback_slots: Vec, #[case] expected_has_callbacks: bool, ) -> anyhow::Result<()> { - let basic_faucet = BasicFungibleFaucet::new("CBK".try_into()?, 8, Felt::new(1_000_000))?; + let faucet_metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("").expect("empty string is a valid token name"), + "CBK".try_into()?, + 8, + 1_000_000u64, + ) + .build()?; let account = AccountBuilder::new([1u8; 32]) .storage_mode(AccountStorageMode::Public) .account_type(AccountType::FungibleFaucet) - .with_component(basic_faucet) + .with_component(faucet_metadata) + .with_component(BasicFungibleFaucet) .with_component(MockAccountComponent::with_slots(callback_slots)) .with_auth_component(Auth::IncrNonce) .build_existing()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs b/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs index a2b827a536..cb27f1fbbf 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_callbacks.rs @@ -32,6 +32,7 @@ use miden_protocol::note::{NoteTag, NoteType}; use miden_protocol::utils::sync::LazyLock; use miden_protocol::{Felt, Word}; use miden_standards::account::faucets::BasicFungibleFaucet; +use miden_standards::account::metadata::{FungibleTokenMetadataBuilder, TokenName}; use miden_standards::code_builder::CodeBuilder; use miden_standards::procedure_digest; use miden_standards::testing::account_component::MockFaucetComponent; @@ -683,7 +684,13 @@ fn add_faucet_with_callbacks( callbacks = callbacks.on_before_asset_added_to_note(proc_root); } - let basic_faucet = BasicFungibleFaucet::new("SYM".try_into()?, 8, Felt::new(1_000_000))?; + let faucet_metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("").expect("empty string is a valid token name"), + "SYM".try_into()?, + 8, + 1_000_000u64, + ) + .build()?; let callback_storage_slots = callbacks.into_storage_slots(); let callback_metadata = @@ -695,7 +702,8 @@ fn add_faucet_with_callbacks( let account_builder = AccountBuilder::new([42; 32]) .storage_mode(AccountStorageMode::Public) .account_type(AccountType::FungibleFaucet) - .with_component(basic_faucet) + .with_component(faucet_metadata) + .with_component(BasicFungibleFaucet) .with_component(callback_component); builder.add_account_from_builder( diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 468953f38b..bbdf85ffed 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -49,6 +49,11 @@ use miden_protocol::transaction::{OrderedTransactionHeaders, RawOutputNote, Tran use miden_protocol::{Felt, MAX_OUTPUT_NOTES_PER_BATCH, Word}; use miden_standards::account::access::Ownable2Step; use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; +use miden_standards::account::metadata::{ + FungibleTokenMetadata, + FungibleTokenMetadataBuilder, + TokenName, +}; use miden_standards::account::mint_policies::{ AuthControlled, OwnerControlled, @@ -328,17 +333,23 @@ impl MockChainBuilder { token_symbol: &str, max_supply: u64, ) -> anyhow::Result { + let name = TokenName::new(token_symbol)?; let token_symbol = TokenSymbol::new(token_symbol) .with_context(|| format!("invalid token symbol: {token_symbol}"))?; - let max_supply_felt = Felt::try_from(max_supply)?; - let basic_faucet = - BasicFungibleFaucet::new(token_symbol, DEFAULT_FAUCET_DECIMALS, max_supply_felt) - .context("failed to create BasicFungibleFaucet")?; + let metadata = FungibleTokenMetadataBuilder::new( + name, + token_symbol, + DEFAULT_FAUCET_DECIMALS, + max_supply, + ) + .build() + .context("failed to create FungibleTokenMetadata")?; let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Public) .account_type(AccountType::FungibleFaucet) - .with_component(basic_faucet) + .with_component(metadata) + .with_component(BasicFungibleFaucet) .with_component(AuthControlled::allow_all()); self.add_account_from_builder(auth_method, account_builder, AccountState::New) @@ -355,19 +366,24 @@ impl MockChainBuilder { max_supply: u64, token_supply: Option, ) -> anyhow::Result { - let max_supply = Felt::try_from(max_supply)?; let token_supply = Felt::try_from(token_supply.unwrap_or(0))?; + let name = TokenName::new(token_symbol)?; let token_symbol = TokenSymbol::new(token_symbol).context("failed to create token symbol")?; - - let basic_faucet = - BasicFungibleFaucet::new(token_symbol, DEFAULT_FAUCET_DECIMALS, max_supply) - .and_then(|fungible_faucet| fungible_faucet.with_token_supply(token_supply)) - .context("failed to create basic fungible faucet")?; + let metadata = FungibleTokenMetadataBuilder::new( + name, + token_symbol, + DEFAULT_FAUCET_DECIMALS, + max_supply, + ) + .token_supply(token_supply) + .build() + .context("failed to create fungible token metadata")?; let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Public) - .with_component(basic_faucet) + .with_component(metadata) + .with_component(BasicFungibleFaucet) .with_component(AuthControlled::allow_all()) .account_type(AccountType::FungibleFaucet); @@ -385,19 +401,25 @@ impl MockChainBuilder { token_supply: Option, mint_policy: OwnerControlledInitConfig, ) -> anyhow::Result { - let max_supply = Felt::try_from(max_supply)?; let token_supply = Felt::try_from(token_supply.unwrap_or(0))?; + let name = TokenName::new(token_symbol)?; let token_symbol = TokenSymbol::new(token_symbol).context("failed to create token symbol")?; - let network_faucet = - NetworkFungibleFaucet::new(token_symbol, DEFAULT_FAUCET_DECIMALS, max_supply) - .and_then(|fungible_faucet| fungible_faucet.with_token_supply(token_supply)) - .context("failed to create network fungible faucet")?; + let metadata = FungibleTokenMetadataBuilder::new( + name, + token_symbol, + DEFAULT_FAUCET_DECIMALS, + max_supply, + ) + .token_supply(token_supply) + .build() + .context("failed to create fungible token metadata")?; let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Network) - .with_component(network_faucet) + .with_component(metadata) + .with_component(NetworkFungibleFaucet) .with_component(Ownable2Step::new(owner_account_id)) .with_component(OwnerControlled::new(mint_policy)) .account_type(AccountType::FungibleFaucet); @@ -406,6 +428,25 @@ impl MockChainBuilder { self.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists) } + /// Adds an existing network fungible faucet account with the given metadata component + /// (for testing metadata::fungible procedures: owner can update description / logo_uri / + /// external_link / max supply when mutable). + pub fn add_existing_network_faucet_with_metadata( + &mut self, + owner_account_id: AccountId, + metadata: FungibleTokenMetadata, + ) -> anyhow::Result { + let account_builder = AccountBuilder::new(self.rng.random()) + .storage_mode(AccountStorageMode::Network) + .with_component(metadata) + .with_component(NetworkFungibleFaucet) + .with_component(Ownable2Step::new(owner_account_id)) + .with_component(OwnerControlled::new(OwnerControlledInitConfig::OwnerOnly)) + .account_type(AccountType::FungibleFaucet); + + self.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists) + } + /// Creates a new public account with an [`MockAccountComponent`] and registers the /// authenticator (if any). pub fn create_new_mock_account(&mut self, auth_method: Auth) -> anyhow::Result { diff --git a/crates/miden-testing/src/standards/mod.rs b/crates/miden-testing/src/standards/mod.rs index 76cf06a85d..b0f8c808a7 100644 --- a/crates/miden-testing/src/standards/mod.rs +++ b/crates/miden-testing/src/standards/mod.rs @@ -1,2 +1,3 @@ mod network_account_target; mod note_tag; +mod token_metadata; diff --git a/crates/miden-testing/src/standards/token_metadata.rs b/crates/miden-testing/src/standards/token_metadata.rs new file mode 100644 index 0000000000..1360f6fbd8 --- /dev/null +++ b/crates/miden-testing/src/standards/token_metadata.rs @@ -0,0 +1,1242 @@ +//! Integration tests for the Token Metadata standard (`FungibleTokenMetadata`). + +extern crate alloc; + +use alloc::sync::Arc; +use alloc::vec::Vec; + +use miden_crypto::hash::poseidon2::Poseidon2; +use miden_processor::crypto::random::RandomCoin; +use miden_protocol::account::{ + Account, + AccountBuilder, + AccountComponent, + AccountId, + AccountIdVersion, + AccountStorageMode, + AccountType, + StorageSlotName, +}; +use miden_protocol::assembly::DefaultSourceManager; +use miden_protocol::asset::TokenSymbol; +use miden_protocol::errors::MasmError; +use miden_protocol::note::{NoteTag, NoteType}; +use miden_protocol::{Felt, Word}; +use miden_standards::account::access::Ownable2Step; +use miden_standards::account::auth::NoAuth; +use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; +use miden_standards::account::metadata::{ + Description, + ExternalLink, + FungibleTokenMetadata, + FungibleTokenMetadataBuilder, + LogoURI, + TokenMetadata, + TokenName, +}; +use miden_standards::code_builder::CodeBuilder; +use miden_standards::errors::standards::{ + ERR_DESCRIPTION_NOT_MUTABLE, + ERR_EXTERNAL_LINK_NOT_MUTABLE, + ERR_LOGO_URI_NOT_MUTABLE, + ERR_MAX_SUPPLY_NOT_MUTABLE, + ERR_SENDER_NOT_OWNER, +}; +use miden_standards::testing::note::NoteBuilder; +use miden_standards::utils::FixedWidthStringError; + +use crate::{MockChain, TransactionContextBuilder, assert_transaction_executor_error}; + +// SHARED HELPERS +// ================================================================================================ + +/// Builds [`FungibleTokenMetadata`] for tests that use raw word arrays + mutability flags +/// (e.g. from [`description_config`] / [`logo_uri_config`] / [`external_link_config`]). +fn network_faucet_metadata( + token_symbol: &str, + max_supply: u64, + token_supply: Option, + max_supply_mutable: bool, + description: Option<([Word; 7], bool)>, + logo_uri: Option<([Word; 7], bool)>, + external_link: Option<([Word; 7], bool)>, +) -> anyhow::Result { + let token_supply = Felt::try_from(token_supply.unwrap_or(0))?; + let name = TokenName::new(token_symbol) + .unwrap_or_else(|_| TokenName::new("").expect("empty string is a valid token name")); + let token_symbol = TokenSymbol::new(token_symbol)?; + + let mut builder = FungibleTokenMetadataBuilder::new(name, token_symbol, 10, max_supply) + .token_supply(token_supply) + .is_max_supply_mutable(max_supply_mutable); + if let Some((words, mutable)) = description { + builder = builder + .description(Description::try_from_words(&words).expect("valid description words")) + .is_description_mutable(mutable); + } + if let Some((words, mutable)) = logo_uri { + builder = builder + .logo_uri(LogoURI::try_from_words(&words).expect("valid logo_uri words")) + .is_logo_uri_mutable(mutable); + } + if let Some((words, mutable)) = external_link { + builder = builder + .external_link(ExternalLink::try_from_words(&words).expect("valid external_link words")) + .is_external_link_mutable(mutable); + } + + Ok(builder.build()?) +} + +fn initial_field_data() -> [Word; 7] { + [ + Word::from([1u32, 2, 3, 4]), + Word::from([5u32, 6, 7, 8]), + Word::from([9u32, 10, 11, 12]), + Word::from([13u32, 14, 15, 16]), + Word::from([17u32, 18, 19, 20]), + Word::from([21u32, 22, 23, 24]), + Word::from([25u32, 26, 27, 28]), + ] +} + +fn new_field_data() -> [Word; 7] { + [ + Word::from([100u32, 101, 102, 103]), + Word::from([104u32, 105, 106, 107]), + Word::from([108u32, 109, 110, 111]), + Word::from([112u32, 113, 114, 115]), + Word::from([116u32, 117, 118, 119]), + Word::from([120u32, 121, 122, 123]), + Word::from([124u32, 125, 126, 127]), + ] +} + +fn owner_account_id() -> AccountId { + AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ) +} + +fn non_owner_account_id() -> AccountId { + AccountId::dummy( + [2; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ) +} + +/// Build a minimal faucet metadata (no optional fields). +fn build_faucet_metadata() -> FungibleTokenMetadata { + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .build() + .unwrap() +} + +/// Build a standard POL faucet metadata (used by scalar getter tests). +fn build_pol_faucet_metadata() -> FungibleTokenMetadata { + FungibleTokenMetadataBuilder::new( + TokenName::new("POL").unwrap(), + TokenSymbol::new("POL").unwrap(), + 8, + 1_000_000u64, + ) + .build() + .unwrap() +} + +/// Build a basic faucet account with POL metadata. +fn build_pol_faucet_account() -> Account { + AccountBuilder::new([4u8; 32]) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(NoAuth) + .with_component(build_pol_faucet_metadata()) + .with_component(BasicFungibleFaucet) + .build() + .unwrap() +} + +/// Flatten `[Word; 7]` into `Vec` for advice map values. +fn field_advice_map_value(field: &[Word; 7]) -> Vec { + let mut value = Vec::with_capacity(28); + for word in field.iter() { + value.extend(word.iter()); + } + value +} + +/// Compute the Poseidon2 hash of the field data (used as the advice map key). +fn compute_field_hash(data: &[Word; 7]) -> Word { + let felts = field_advice_map_value(data); + Poseidon2::hash_elements(&felts) +} + +/// Execute a tx script against the given account and assert success. +async fn execute_tx_script( + account: Account, + tx_script_code: impl AsRef, +) -> anyhow::Result<()> { + let source_manager = Arc::new(DefaultSourceManager::default()); + let tx_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_tx_script(tx_script_code.as_ref())?; + let tx_context = TransactionContextBuilder::new(account) + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()?; + tx_context.execute().await?; + Ok(()) +} + +// ================================================================================================= +// GETTER TESTS – name +// ================================================================================================= + +#[tokio::test] +async fn get_name_from_masm() -> anyhow::Result<()> { + let token_name = TokenName::new("test name").unwrap(); + let name = token_name.to_words(); + + let metadata = + FungibleTokenMetadataBuilder::new(token_name, "TST".try_into().unwrap(), 2, 1_000u64) + .build() + .unwrap(); + + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build()?; + + execute_tx_script( + account, + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_name + push.{n0} + assert_eqw.err="name chunk 0 does not match" + push.{n1} + assert_eqw.err="name chunk 1 does not match" + end + "#, + n0 = name[0], + n1 = name[1], + ), + ) + .await +} + +#[tokio::test] +async fn get_name_zeros_returns_empty() -> anyhow::Result<()> { + // Build a faucet with an empty name to verify get_name returns zero words. + let metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("").expect("empty string is a valid token name"), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .build() + .unwrap(); + + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build()?; + + execute_tx_script( + account, + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_name + padw assert_eqw.err="name chunk 0 should be empty" + padw assert_eqw.err="name chunk 1 should be empty" + end + "#, + ) + .await +} + +// ================================================================================================= +// GETTER TESTS – scalar fields +// ================================================================================================= + +#[tokio::test] +async fn faucet_get_decimals() -> anyhow::Result<()> { + let expected = Felt::from(8u8).as_canonical_u64(); + execute_tx_script( + build_pol_faucet_account(), + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_decimals + push.{expected} assert_eq.err="decimals does not match" + push.0 assert_eq.err="clean stack: pad must be 0" + end + "# + ), + ) + .await +} + +#[tokio::test] +async fn faucet_get_token_symbol() -> anyhow::Result<()> { + let expected = Felt::from(TokenSymbol::new("POL").unwrap()).as_canonical_u64(); + execute_tx_script( + build_pol_faucet_account(), + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_token_symbol + push.{expected} assert_eq.err="token_symbol does not match" + push.0 assert_eq.err="clean stack: pad must be 0" + end + "# + ), + ) + .await +} + +#[tokio::test] +async fn faucet_get_token_supply() -> anyhow::Result<()> { + execute_tx_script( + build_pol_faucet_account(), + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_token_supply + push.0 assert_eq.err="token_supply does not match" + push.0 assert_eq.err="clean stack: pad must be 0" + end + "#, + ) + .await +} + +#[tokio::test] +async fn faucet_get_max_supply() -> anyhow::Result<()> { + let expected = Felt::new(1_000_000).as_canonical_u64(); + execute_tx_script( + build_pol_faucet_account(), + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_max_supply + push.{expected} assert_eq.err="max_supply does not match" + push.0 assert_eq.err="clean stack: pad must be 0" + end + "# + ), + ) + .await +} + +#[tokio::test] +async fn faucet_get_token_metadata() -> anyhow::Result<()> { + let symbol = TokenSymbol::new("POL").unwrap(); + let expected_symbol = Felt::from(symbol).as_canonical_u64(); + let expected_decimals = Felt::from(8u8).as_canonical_u64(); + let expected_max_supply = Felt::new(1_000_000).as_canonical_u64(); + + execute_tx_script( + build_pol_faucet_account(), + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_token_metadata + push.0 assert_eq.err="token_supply does not match" + push.{expected_max_supply} assert_eq.err="max_supply does not match" + push.{expected_decimals} assert_eq.err="decimals does not match" + push.{expected_symbol} assert_eq.err="token_symbol does not match" + end + "# + ), + ) + .await +} + +#[tokio::test] +async fn faucet_get_decimals_symbol_and_max_supply() -> anyhow::Result<()> { + let symbol = TokenSymbol::new("POL").unwrap(); + let expected_decimals = Felt::from(8u8).as_canonical_u64(); + let expected_symbol = Felt::from(symbol).as_canonical_u64(); + let expected_max_supply = Felt::new(1_000_000).as_canonical_u64(); + + execute_tx_script( + build_pol_faucet_account(), + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_decimals + push.{expected_decimals} assert_eq.err="decimals does not match" + push.0 + call.::miden::standards::metadata::fungible_faucet::get_token_symbol + push.{expected_symbol} assert_eq.err="token_symbol does not match" + push.0 + call.::miden::standards::metadata::fungible_faucet::get_max_supply + push.{expected_max_supply} assert_eq.err="max_supply does not match" + end + "# + ), + ) + .await +} + +// ================================================================================================= +// GETTER TESTS – mutability config +// ================================================================================================= + +#[tokio::test] +async fn get_mutability_config() -> anyhow::Result<()> { + let metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .description(Description::new("test").unwrap()) + .is_description_mutable(true) + .is_max_supply_mutable(true) + .build() + .unwrap(); + + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build()?; + + execute_tx_script( + account, + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_mutability_config + push.1 assert_eq.err="desc_mutable should be 1" + push.0 assert_eq.err="logo_mutable should be 0" + push.0 assert_eq.err="extlink_mutable should be 0" + push.1 assert_eq.err="max_supply_mutable should be 1" + end + "#, + ) + .await +} + +/// Tests all `is_*_mutable` procedures with flag=0 and flag=1. +#[tokio::test] +async fn is_field_mutable_checks() -> anyhow::Result<()> { + let desc = Description::new("test").unwrap(); + let logo = LogoURI::new("https://example.com/logo").unwrap(); + let link = ExternalLink::new("https://example.com").unwrap(); + + // (metadata_builder, proc_name, expected_value) + let cases: Vec<(FungibleTokenMetadata, &str, u8)> = vec![ + ( + build_faucet_metadata().with_max_supply_mutable(true), + "is_max_supply_mutable", + 1, + ), + ( + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .description(desc.clone()) + .is_description_mutable(true) + .build() + .unwrap(), + "is_description_mutable", + 1, + ), + ( + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .description(desc) + .build() + .unwrap(), + "is_description_mutable", + 0, + ), + ( + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .logo_uri(logo.clone()) + .is_logo_uri_mutable(true) + .build() + .unwrap(), + "is_logo_uri_mutable", + 1, + ), + ( + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .logo_uri(logo) + .build() + .unwrap(), + "is_logo_uri_mutable", + 0, + ), + ( + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .external_link(link.clone()) + .is_external_link_mutable(true) + .build() + .unwrap(), + "is_external_link_mutable", + 1, + ), + ( + FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .external_link(link) + .build() + .unwrap(), + "is_external_link_mutable", + 0, + ), + ]; + + for (metadata, proc_name, expected) in cases { + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build()?; + + execute_tx_script( + account, + format!( + "begin + call.::miden::standards::metadata::fungible_faucet::{proc_name} + push.{expected} + assert_eq.err=\"{proc_name} returned unexpected value\" + end" + ), + ) + .await?; + } + + Ok(()) +} + +// ================================================================================================= +// STORAGE LAYOUT TESTS +// ================================================================================================= + +#[test] +fn faucet_with_metadata_storage_layout() { + let token_name = TokenName::new("test faucet name").unwrap(); + let desc_text = "faucet description text for testing"; + let description = Description::new(desc_text).unwrap(); + let desc_words = description.to_words(); + + let metadata = + FungibleTokenMetadataBuilder::new(token_name, "TST".try_into().unwrap(), 8, 1_000_000u64) + .description(description) + .build() + .unwrap(); + + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build() + .unwrap(); + + // Verify faucet metadata + let faucet_metadata = + account.storage().get_item(FungibleTokenMetadata::metadata_slot()).unwrap(); + assert_eq!(faucet_metadata[0], Felt::new(0)); + assert_eq!(faucet_metadata[1], Felt::new(1_000_000)); + assert_eq!(faucet_metadata[2], Felt::new(8)); + + // Verify description + for (i, expected) in desc_words.iter().enumerate() { + let chunk = account.storage().get_item(TokenMetadata::description_slot(i)).unwrap(); + assert_eq!(chunk, *expected); + } +} + +#[test] +fn name_32_bytes_accepted() { + let max_name = "a".repeat(TokenName::MAX_BYTES); + let token_name = TokenName::new(&max_name).unwrap(); + let metadata = + FungibleTokenMetadataBuilder::new(token_name, "TST".try_into().unwrap(), 2, 1_000u64) + .build() + .unwrap(); + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build() + .unwrap(); + let name_0 = account.storage().get_item(TokenMetadata::name_chunk_0_slot()).unwrap(); + let name_1 = account.storage().get_item(TokenMetadata::name_chunk_1_slot()).unwrap(); + let decoded = TokenName::try_from_words(&[name_0, name_1]).unwrap(); + assert_eq!(decoded.as_str(), max_name); +} + +#[test] +fn name_33_bytes_rejected() { + let result = TokenName::new(&"a".repeat(33)); + assert!(matches!(result, Err(FixedWidthStringError::TooLong { max: 32, actual: 33 }))); +} + +#[test] +fn description_7_words_full_capacity() { + let desc_text = "a".repeat(Description::MAX_BYTES); + let description = Description::new(&desc_text).unwrap(); + let desc_words = description.to_words(); + let metadata = FungibleTokenMetadataBuilder::new( + TokenName::new("T").unwrap(), + "TST".try_into().unwrap(), + 2, + 1_000u64, + ) + .description(description) + .build() + .unwrap(); + let account = AccountBuilder::new([1u8; 32]) + .account_type(AccountType::FungibleFaucet) + .with_auth_component(NoAuth) + .with_component(metadata) + .with_component(BasicFungibleFaucet) + .build() + .unwrap(); + for (i, expected) in desc_words.iter().enumerate() { + let chunk = account.storage().get_item(TokenMetadata::description_slot(i)).unwrap(); + assert_eq!(chunk, *expected); + } +} + +// ================================================================================================= +// FAUCET INITIALIZATION – basic + network with max name/description +// ================================================================================================= + +fn verify_faucet_with_max_name_and_description( + seed: [u8; 32], + symbol: &str, + max_supply: u64, + storage_mode: AccountStorageMode, + extra_components: Vec, +) { + let max_name = "a".repeat(TokenName::MAX_BYTES); + let desc_text = "a".repeat(Description::MAX_BYTES); + let description = Description::new(&desc_text).unwrap(); + let desc_words = description.to_words(); + + let faucet_metadata = FungibleTokenMetadataBuilder::new( + TokenName::new(&max_name).unwrap(), + symbol.try_into().unwrap(), + 6, + max_supply, + ) + .description(description) + .build() + .unwrap(); + + let mut builder = AccountBuilder::new(seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(storage_mode) + .with_auth_component(NoAuth) + .with_component(faucet_metadata); + + for comp in extra_components { + builder = builder.with_component(comp); + } + + let account = builder.build().unwrap(); + + let name_words = TokenName::new(&max_name).unwrap().to_words(); + let name_0 = account.storage().get_item(TokenMetadata::name_chunk_0_slot()).unwrap(); + let name_1 = account.storage().get_item(TokenMetadata::name_chunk_1_slot()).unwrap(); + assert_eq!(name_0, name_words[0]); + assert_eq!(name_1, name_words[1]); + for (i, expected) in desc_words.iter().enumerate() { + let chunk = account.storage().get_item(TokenMetadata::description_slot(i)).unwrap(); + assert_eq!(chunk, *expected); + } + let faucet_metadata_val = + account.storage().get_item(FungibleTokenMetadata::metadata_slot()).unwrap(); + assert_eq!(faucet_metadata_val[1], Felt::new(max_supply)); +} + +#[test] +fn basic_faucet_with_max_name_and_full_description() { + verify_faucet_with_max_name_and_description( + [5u8; 32], + "MAX", + 1_000_000, + AccountStorageMode::Public, + vec![BasicFungibleFaucet.into()], + ); +} + +#[test] +fn network_faucet_with_max_name_and_full_description() { + verify_faucet_with_max_name_and_description( + [6u8; 32], + "NET", + 2_000_000, + AccountStorageMode::Network, + vec![NetworkFungibleFaucet.into(), Ownable2Step::new(owner_account_id()).into()], + ); +} + +// ================================================================================================= +// MASM NAME READBACK – basic + network faucets +// ================================================================================================= + +#[tokio::test] +async fn basic_faucet_name_readable_from_masm() -> anyhow::Result<()> { + let token_name = TokenName::new("readable name").unwrap(); + let name = token_name.to_words(); + + let faucet_metadata = + FungibleTokenMetadataBuilder::new(token_name, "MAS".try_into().unwrap(), 10, 999_999u64) + .description(Description::new("readable description").unwrap()) + .build() + .unwrap(); + + let account = AccountBuilder::new([3u8; 32]) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(NoAuth) + .with_component(faucet_metadata) + .with_component(BasicFungibleFaucet) + .build()?; + + execute_tx_script( + account, + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_name + push.{n0} + assert_eqw.err="faucet name chunk 0 does not match" + push.{n1} + assert_eqw.err="faucet name chunk 1 does not match" + end + "#, + n0 = name[0], + n1 = name[1], + ), + ) + .await +} + +#[tokio::test] +async fn network_faucet_name_readable_from_masm() -> anyhow::Result<()> { + let max_name = "b".repeat(TokenName::MAX_BYTES); + let name_words = TokenName::new(&max_name).unwrap().to_words(); + + let network_faucet_metadata = FungibleTokenMetadataBuilder::new( + TokenName::new(&max_name).unwrap(), + "MAS".try_into().unwrap(), + 6, + 1_000_000u64, + ) + .description(Description::new("network faucet description").unwrap()) + .build() + .unwrap(); + + let account = AccountBuilder::new([7u8; 32]) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Network) + .with_auth_component(NoAuth) + .with_component(network_faucet_metadata) + .with_component(NetworkFungibleFaucet) + .with_component(Ownable2Step::new(owner_account_id())) + .build()?; + + execute_tx_script( + account, + format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::get_name + push.{n0} + assert_eqw.err="network faucet name chunk 0 does not match" + push.{n1} + assert_eqw.err="network faucet name chunk 1 does not match" + end + "#, + n0 = name_words[0], + n1 = name_words[1], + ), + ) + .await +} + +// ================================================================================================= +// SETTER TESTS – set_description, set_logo_uri, set_external_link (parameterised) +// ================================================================================================= + +struct FieldSetterFaucetArgs { + description: Option<([Word; 7], bool)>, + logo_uri: Option<([Word; 7], bool)>, + external_link: Option<([Word; 7], bool)>, +} + +fn description_config(data: [Word; 7], mutable: bool) -> FieldSetterFaucetArgs { + FieldSetterFaucetArgs { + description: Some((data, mutable)), + logo_uri: None, + external_link: None, + } +} + +fn logo_uri_config(data: [Word; 7], mutable: bool) -> FieldSetterFaucetArgs { + FieldSetterFaucetArgs { + description: None, + logo_uri: Some((data, mutable)), + external_link: None, + } +} + +fn external_link_config(data: [Word; 7], mutable: bool) -> FieldSetterFaucetArgs { + FieldSetterFaucetArgs { + description: None, + logo_uri: None, + external_link: Some((data, mutable)), + } +} + +async fn test_field_setter_immutable_fails( + proc_name: &str, + immutable_error: MasmError, + args: FieldSetterFaucetArgs, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let owner = owner_account_id(); + + let metadata = network_faucet_metadata( + "FLD", + 1000, + Some(0), + false, + args.description, + args.logo_uri, + args.external_link, + )?; + let faucet = builder.add_existing_network_faucet_with_metadata(owner, metadata)?; + let mock_chain = builder.build()?; + + let tx_script_code = format!( + r#" + begin + call.::miden::standards::metadata::fungible_faucet::{proc_name} + end + "# + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let tx_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_tx_script(&tx_script_code)?; + + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[], &[])? + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()?; + + let result = tx_context.execute().await; + assert_transaction_executor_error!(result, immutable_error); + + Ok(()) +} + +async fn test_field_setter_owner_succeeds( + proc_name: &str, + args: FieldSetterFaucetArgs, + slot_fn: fn(usize) -> &'static StorageSlotName, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let owner = owner_account_id(); + let new_data = new_field_data(); + + let metadata = network_faucet_metadata( + "FLD", + 1000, + Some(0), + false, + args.description, + args.logo_uri, + args.external_link, + )?; + let faucet = builder.add_existing_network_faucet_with_metadata(owner, metadata)?; + let mock_chain = builder.build()?; + + let hash = compute_field_hash(&new_data); + let hash_elems = hash.as_elements(); + + // Push hash as 4 Felts so advice map key matches; dropw after call so stack depth is 16 + // (setter leaves 20). Use `debug.stack` in the script and run with --nocapture to trace. + let note_script_code = format!( + r#" + begin + dropw push.{e3} push.{e2} push.{e1} push.{e0} + call.::miden::standards::metadata::fungible_faucet::{proc_name} + dropw + end +"#, + e0 = hash_elems[0], + e1 = hash_elems[1], + e2 = hash_elems[2], + e3 = hash_elems[3], + proc_name = proc_name, + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let note_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_note_script(¬e_script_code)?; + + let mut rng = RandomCoin::new([Felt::from(42u32); 4].into()); + let note = NoteBuilder::new(owner, &mut rng) + .note_type(NoteType::Private) + .tag(NoteTag::default().into()) + .serial_number(Word::from([7, 8, 9, 10u32])) + .code(¬e_script_code) + .build()?; + + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[], &[note])? + .add_note_script(note_script) + .extend_advice_map([(hash, field_advice_map_value(&new_data))]) + .with_source_manager(source_manager) + .build()?; + + let executed = tx_context.execute().await?; + let mut updated_faucet = faucet.clone(); + updated_faucet.apply_delta(executed.account_delta())?; + + for (i, expected) in new_data.iter().enumerate() { + let chunk = updated_faucet.storage().get_item(slot_fn(i))?; + assert_eq!(chunk, *expected, "field chunk {i} should be updated"); + } + + Ok(()) +} + +async fn test_field_setter_non_owner_fails( + proc_name: &str, + args: FieldSetterFaucetArgs, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let owner = owner_account_id(); + let non_owner = non_owner_account_id(); + let new_data = new_field_data(); + + let metadata = network_faucet_metadata( + "FLD", + 1000, + Some(0), + false, + args.description, + args.logo_uri, + args.external_link, + )?; + let faucet = builder.add_existing_network_faucet_with_metadata(owner, metadata)?; + let mock_chain = builder.build()?; + + let hash = compute_field_hash(&new_data); + let hash_elems = hash.as_elements(); + + let note_script_code = format!( + r#" + begin + dropw push.{e3} push.{e2} push.{e1} push.{e0} + call.::miden::standards::metadata::fungible_faucet::{proc_name} + dropw + end +"#, + e0 = hash_elems[0], + e1 = hash_elems[1], + e2 = hash_elems[2], + e3 = hash_elems[3], + proc_name = proc_name, + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let note_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_note_script(¬e_script_code)?; + + let mut rng = RandomCoin::new([Felt::from(99u32); 4].into()); + let note = NoteBuilder::new(non_owner, &mut rng) + .note_type(NoteType::Private) + .tag(NoteTag::default().into()) + .serial_number(Word::from([11, 12, 13, 14u32])) + .code(¬e_script_code) + .build()?; + + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[], &[note])? + .add_note_script(note_script) + .extend_advice_map([(hash, field_advice_map_value(&new_data))]) + .with_source_manager(source_manager) + .build()?; + + let result = tx_context.execute().await; + assert_transaction_executor_error!(result, ERR_SENDER_NOT_OWNER); + + Ok(()) +} + +// --- set_description --- + +#[tokio::test] +async fn set_description_immutable_fails() -> anyhow::Result<()> { + test_field_setter_immutable_fails( + "set_description", + ERR_DESCRIPTION_NOT_MUTABLE, + description_config(initial_field_data(), false), + ) + .await +} + +#[tokio::test] +async fn set_description_mutable_owner_succeeds() -> anyhow::Result<()> { + test_field_setter_owner_succeeds( + "set_description", + description_config(initial_field_data(), true), + TokenMetadata::description_slot, + ) + .await +} + +#[tokio::test] +async fn set_description_mutable_non_owner_fails() -> anyhow::Result<()> { + test_field_setter_non_owner_fails( + "set_description", + description_config(initial_field_data(), true), + ) + .await +} + +// --- set_logo_uri --- + +#[tokio::test] +async fn set_logo_uri_immutable_fails() -> anyhow::Result<()> { + test_field_setter_immutable_fails( + "set_logo_uri", + ERR_LOGO_URI_NOT_MUTABLE, + logo_uri_config(initial_field_data(), false), + ) + .await +} + +#[tokio::test] +async fn set_logo_uri_mutable_owner_succeeds() -> anyhow::Result<()> { + test_field_setter_owner_succeeds( + "set_logo_uri", + logo_uri_config(initial_field_data(), true), + TokenMetadata::logo_uri_slot, + ) + .await +} + +#[tokio::test] +async fn set_logo_uri_mutable_non_owner_fails() -> anyhow::Result<()> { + test_field_setter_non_owner_fails("set_logo_uri", logo_uri_config(initial_field_data(), true)) + .await +} + +// --- set_external_link --- + +#[tokio::test] +async fn set_external_link_immutable_fails() -> anyhow::Result<()> { + test_field_setter_immutable_fails( + "set_external_link", + ERR_EXTERNAL_LINK_NOT_MUTABLE, + external_link_config(initial_field_data(), false), + ) + .await +} + +#[tokio::test] +async fn set_external_link_mutable_owner_succeeds() -> anyhow::Result<()> { + test_field_setter_owner_succeeds( + "set_external_link", + external_link_config(initial_field_data(), true), + TokenMetadata::external_link_slot, + ) + .await +} + +#[tokio::test] +async fn set_external_link_mutable_non_owner_fails() -> anyhow::Result<()> { + test_field_setter_non_owner_fails( + "set_external_link", + external_link_config(initial_field_data(), true), + ) + .await +} + +// ================================================================================================= +// SETTER TESTS – set_max_supply +// ================================================================================================= + +#[tokio::test] +async fn set_max_supply_immutable_fails() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let owner = owner_account_id(); + + let metadata = network_faucet_metadata("MSM", 1000, Some(0), false, None, None, None)?; + let faucet = builder.add_existing_network_faucet_with_metadata(owner, metadata)?; + let mock_chain = builder.build()?; + + let tx_script_code = r#" + begin + push.2000 + call.::miden::standards::metadata::fungible_faucet::set_max_supply + end + "#; + + let source_manager = Arc::new(DefaultSourceManager::default()); + let tx_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_tx_script(tx_script_code)?; + + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[], &[])? + .tx_script(tx_script) + .with_source_manager(source_manager) + .build()?; + + let result = tx_context.execute().await; + assert_transaction_executor_error!(result, ERR_MAX_SUPPLY_NOT_MUTABLE); + + Ok(()) +} + +#[tokio::test] +async fn set_max_supply_mutable_owner_succeeds() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let owner = owner_account_id(); + let new_max_supply: u64 = 2000; + + let metadata = network_faucet_metadata("MSM", 1000, Some(0), true, None, None, None)?; + let faucet = builder.add_existing_network_faucet_with_metadata(owner, metadata)?; + let mock_chain = builder.build()?; + + let note_script_code = format!( + r#" + begin + push.{new_max_supply} + swap drop + call.::miden::standards::metadata::fungible_faucet::set_max_supply + end + "# + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let note_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_note_script(¬e_script_code)?; + + let mut rng = RandomCoin::new([Felt::from(42u32); 4].into()); + let note = NoteBuilder::new(owner, &mut rng) + .note_type(NoteType::Private) + .tag(NoteTag::default().into()) + .serial_number(Word::from([20, 21, 22, 23u32])) + .code(¬e_script_code) + .build()?; + + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[], &[note])? + .add_note_script(note_script) + .with_source_manager(source_manager) + .build()?; + + let executed = tx_context.execute().await?; + let mut updated_faucet = faucet.clone(); + updated_faucet.apply_delta(executed.account_delta())?; + + let metadata_word = updated_faucet.storage().get_item(FungibleTokenMetadata::metadata_slot())?; + assert_eq!(metadata_word[1], Felt::new(new_max_supply), "max_supply should be updated"); + assert_eq!(metadata_word[0], Felt::new(0), "token_supply should remain unchanged"); + + Ok(()) +} + +#[tokio::test] +async fn set_max_supply_mutable_non_owner_fails() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let owner = owner_account_id(); + let non_owner = non_owner_account_id(); + let new_max_supply: u64 = 2000; + + let metadata = network_faucet_metadata("MSM", 1000, Some(0), true, None, None, None)?; + let faucet = builder.add_existing_network_faucet_with_metadata(owner, metadata)?; + let mock_chain = builder.build()?; + + let note_script_code = format!( + r#" + begin + push.{new_max_supply} + swap drop + call.::miden::standards::metadata::fungible_faucet::set_max_supply + end + "# + ); + + let source_manager = Arc::new(DefaultSourceManager::default()); + let note_script = CodeBuilder::with_source_manager(source_manager.clone()) + .compile_note_script(¬e_script_code)?; + + let mut rng = RandomCoin::new([Felt::from(99u32); 4].into()); + let note = NoteBuilder::new(non_owner, &mut rng) + .note_type(NoteType::Private) + .tag(NoteTag::default().into()) + .serial_number(Word::from([30, 31, 32, 33u32])) + .code(¬e_script_code) + .build()?; + + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[], &[note])? + .add_note_script(note_script) + .with_source_manager(source_manager) + .build()?; + + let result = tx_context.execute().await; + assert_transaction_executor_error!(result, ERR_SENDER_NOT_OWNER); + + Ok(()) +} diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 675bdada2d..061f036afa 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -29,11 +29,8 @@ use miden_protocol::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER; use miden_protocol::transaction::{ExecutedTransaction, RawOutputNote}; use miden_protocol::{Felt, Word}; use miden_standards::account::access::Ownable2Step; -use miden_standards::account::faucets::{ - BasicFungibleFaucet, - NetworkFungibleFaucet, - TokenMetadata, -}; +use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; +use miden_standards::account::metadata::FungibleTokenMetadata; use miden_standards::account::mint_policies::OwnerControlledInitConfig; use miden_standards::code_builder::CodeBuilder; use miden_standards::errors::standards::{ @@ -307,7 +304,7 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R builder.add_output_note(RawOutputNote::Full(note.clone())); let mock_chain = builder.build()?; - let token_metadata = TokenMetadata::try_from(faucet.storage())?; + let token_metadata = FungibleTokenMetadata::try_from(faucet.storage())?; // Check that max_supply at the word's index 0 is 200. The remainder of the word is initialized // with the metadata of the faucet which we don't need to check. @@ -586,7 +583,7 @@ async fn network_faucet_mint() -> anyhow::Result<()> { let mut target_account = builder.add_existing_wallet(Auth::IncrNonce)?; // Check the Network Fungible Faucet's max supply. - let actual_max_supply = TokenMetadata::try_from(faucet.storage())?.max_supply(); + let actual_max_supply = FungibleTokenMetadata::try_from(faucet.storage())?.max_supply(); assert_eq!(actual_max_supply.as_canonical_u64(), max_supply); // Check that the creator account ID is stored in the ownership slot. @@ -602,7 +599,7 @@ async fn network_faucet_mint() -> anyhow::Result<()> { // Check that the faucet's token supply has been correctly initialized. // The already issued amount should be 50. - let initial_token_supply = TokenMetadata::try_from(faucet.storage())?.token_supply(); + let initial_token_supply = FungibleTokenMetadata::try_from(faucet.storage())?.token_supply(); assert_eq!(initial_token_supply.as_canonical_u64(), token_supply); // CREATE MINT NOTE USING STANDARD NOTE @@ -1278,7 +1275,7 @@ async fn network_faucet_burn() -> anyhow::Result<()> { mock_chain.prove_next_block()?; // Check the initial token issuance before burning - let initial_token_supply = TokenMetadata::try_from(faucet.storage())?.token_supply(); + let initial_token_supply = FungibleTokenMetadata::try_from(faucet.storage())?.token_supply(); assert_eq!(initial_token_supply, Felt::new(100)); // EXECUTE BURN NOTE AGAINST NETWORK FAUCET @@ -1295,7 +1292,7 @@ async fn network_faucet_burn() -> anyhow::Result<()> { // Apply the delta to the faucet account and verify the token issuance decreased faucet.apply_delta(executed_transaction.account_delta())?; - let final_token_supply = TokenMetadata::try_from(faucet.storage())?.token_supply(); + let final_token_supply = FungibleTokenMetadata::try_from(faucet.storage())?.token_supply(); assert_eq!( final_token_supply, Felt::new(initial_token_supply.as_canonical_u64() - burn_amount)