From 0f4b263f387bedbb875e756aa618045c21c9c75b Mon Sep 17 00:00:00 2001 From: Lorenzo Benetollo Date: Wed, 17 Jun 2026 16:25:46 +0200 Subject: [PATCH] Add IIP-0013: Dynamic Module Metadata Follow-up to IIP-0010 (Package Metadata): move per-module metadata into dynamic fields so new metadata kinds can be added without changing the package metadata object type. Adds view function metadata (IIP-0011) as the first new kind. --- README.md | 40 +++---- iips/IIP-0013/IIP-0013.md | 234 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 19 deletions(-) create mode 100644 iips/IIP-0013/IIP-0013.md diff --git a/README.md b/README.md index 9cd697d..6de1576 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Building the IOTA ecosystem is a community effort, therefore we welcome anyone t ## Propose new ideas Do you have an idea how to improve the IOTA technology stack? + - Head over to the [discussions](https://github.com/iotaledger/iips/discussions) page to browse already submitted ideas or share yours! - Once your idea is discussed, you can submit a draft IIP ([template here](https://github.com/iotaledger/iips/blob/main/TEMPLATE.md) as a PR to the repository. - You will receive feedback from the IIP Editors, core devs and community members to refine your proposal. @@ -25,28 +26,29 @@ You may find more information about the IIP Process in [IIP-1](./iips/IIP-0001/i ## List of IIPs - - Last updated: 2026-06-10 - - The _Status_ of a IIP reflects its current state with respect to its progression to being supported on the IOTA Mainnet. - - `Draft` IIPs are work in progress. They may or may not have a working implementation on a testnet. - - `Proposed` IIPs are demonstrated to have a working implementation on the IOTA Devnet or Testnet. - - `Active` IIPs are supported on the IOTA Mainnet. - - `Replaced` IIPs have been replaced by a newer IIP. - - `Obsolete` IIPs are no longer in use. +- Last updated: 2026-06-10 +- The _Status_ of a IIP reflects its current state with respect to its progression to being supported on the IOTA Mainnet. + - `Draft` IIPs are work in progress. They may or may not have a working implementation on a testnet. + - `Proposed` IIPs are demonstrated to have a working implementation on the IOTA Devnet or Testnet. + - `Active` IIPs are supported on the IOTA Mainnet. + - `Replaced` IIPs have been replaced by a newer IIP. + - `Obsolete` IIPs are no longer in use. ![image](iips/IIP-0001/process.svg) -| \# | Title | Description | Type | Layer | Status | -|----|----------------------------------------------------------|------------------------------------------------------------------------------------|-----------|-----------|----------| -| 1 | [IIP Process](iips/IIP-0001/iip-0001.md) | Purpose and guidelines of the contribution framework | Process | \- | Active | -| 2 | [Starfish Consensus Protocol](iips/IIP-0002/iip-0002.md) | A DAG-based consensus protocol improving liveness and efficiency | Standards | Core | Active | -| 3 | [Sequencer Improvements](iips/IIP-0003/iip-0003.md) | Improved sequencing algorithm for reducing the number of transaction cancellations | Standards | Core | Active | -| 5 | [Move View Functions](iips/IIP-0005/iip-0005.md) | A standardized interface for application-specific queries to on-chain state | Standards | Interface | Draft | -| 7 | [Validator Scoring Mechanism](iips/IIP-0007/IIP-0007.md) | An automated and standardized system for monitoring validator behavior and scores | Standards | Core | Draft | -| 8 | [Dynamic Minimum Commission based on the Validator's Voting Power per Epoch](iips/IIP-0008/IIP-0008.md) | A dynamic minimum validator commission rate set to the validator's voting power percentage to prevent stake hoarding and promote decentralization | Standards | Core | Active | -| 9 | [Abstract IOTA Accounts](iips/IIP-0009/IIP-0009.md) | Abstract accounts on IOTA enable smart-contract-based authentication of addresses. | Standards | Core | Proposed | -| 10 | [Package Metadata](iips/IIP-0010/IIP-0010.md) | Immutable on-chain object that provides trusted metadata about Move packages during execution | Standards | Core | Proposed | -| 11 | [Core Move View Functions](iips/IIP-0011/IIP-0011.md) | Move view functions - definition and specification at language level. | Standards | Core | Proposed | -| 12 | [Starfish Speed Consensus Protocol](iips/IIP-0012/iip-0012.md) | Optimistic transaction sequencing extension for Starfish that reduces commit latency | Standards | Core | Draft | +| \# | Title | Description | Type | Layer | Status | +| --- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------- | -------- | +| 1 | [IIP Process](iips/IIP-0001/iip-0001.md) | Purpose and guidelines of the contribution framework | Process | \- | Active | +| 2 | [Starfish Consensus Protocol](iips/IIP-0002/iip-0002.md) | A DAG-based consensus protocol improving liveness and efficiency | Standards | Core | Active | +| 3 | [Sequencer Improvements](iips/IIP-0003/iip-0003.md) | Improved sequencing algorithm for reducing the number of transaction cancellations | Standards | Core | Active | +| 5 | [Move View Functions](iips/IIP-0005/iip-0005.md) | A standardized interface for application-specific queries to on-chain state | Standards | Interface | Draft | +| 7 | [Validator Scoring Mechanism](iips/IIP-0007/IIP-0007.md) | An automated and standardized system for monitoring validator behavior and scores | Standards | Core | Draft | +| 8 | [Dynamic Minimum Commission based on the Validator's Voting Power per Epoch](iips/IIP-0008/IIP-0008.md) | A dynamic minimum validator commission rate set to the validator's voting power percentage to prevent stake hoarding and promote decentralization | Standards | Core | Active | +| 9 | [Abstract IOTA Accounts](iips/IIP-0009/IIP-0009.md) | Abstract accounts on IOTA enable smart-contract-based authentication of addresses. | Standards | Core | Proposed | +| 10 | [Package Metadata](iips/IIP-0010/IIP-0010.md) | Immutable on-chain object that provides trusted metadata about Move packages during execution | Standards | Core | Proposed | +| 11 | [Core Move View Functions](iips/IIP-0011/IIP-0011.md) | Move view functions - definition and specification at language level. | Standards | Core | Draft | +| 12 | [Starfish Speed Consensus Protocol](iips/IIP-0012/iip-0012.md) | Optimistic transaction sequencing extension for Starfish that reduces commit latency | Standards | Core | Draft | +| 13 | [Dynamic Module Metadata](iips/IIP-0013/IIP-0013.md) | Store package module metadata in dynamic fields so it can be extended without changing the package metadata object type | Standards | Core | Draft | ## Need help? diff --git a/iips/IIP-0013/IIP-0013.md b/iips/IIP-0013/IIP-0013.md new file mode 100644 index 0000000..017bf17 --- /dev/null +++ b/iips/IIP-0013/IIP-0013.md @@ -0,0 +1,234 @@ +--- +iip: 13 +title: Dynamic Module Metadata +description: Store package module metadata in dynamic fields so it can be extended without changing the package metadata object type. +author: Lorenzo Benetollo (@lollobene) , Mirko Zichichi (@miker83z) +discussions-to: https://github.com/iotaledger/IIPs/discussions/47 +status: Draft +type: Standards Track +layer: Core +created: 2026-06-17 +requires: IIP-0010, IIP-0011 +--- + +## Abstract + +This proposal refines the `PackageMetadata` model defined in [IIP-0010](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0010/IIP-0010.md) by moving per-module metadata out of the immutable `PackageMetadataV1` object and into **dynamic fields**. Instead of embedding an inline `VecMap`, the `PackageMetadataV1` object now records a metadata-layout version and references a map of `ModuleMetadata` objects, each derived deterministically from the package id and module name. Each `ModuleMetadata` is itself an extensible key-value store backed by dynamic fields, where every key holds a distinct kind of module metadata. + +This makes the set of recorded module metadata kinds open-ended: new kinds can be added by writing a new dynamic field, without changing the `PackageMetadata` object type or introducing a new top-level struct version. The first new kind enabled by this layout is **view function metadata** — the list of `#[view]` functions ([IIP-0011](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0011/IIP-0011.md)) per module — recorded alongside the existing authenticator metadata. + +The change is gated behind a protocol feature flag and is backwards compatible: packages published under the previous layout keep working, and the legacy accessors continue to function. + +## Motivation + +[IIP-0010](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0010/IIP-0010.md) stores all module metadata inline in the frozen `PackageMetadataV1` object: + +```move +public struct PackageMetadataV1 has key { + id: UID, + storage_id: ID, + runtime_id: ID, + package_version: u64, + modules_metadata: VecMap, +} + +public struct ModuleMetadataV1 has copy, drop, store { + authenticator_metadata: vector, +} +``` + +This shape couples the _content_ of module metadata to the _type_ of the metadata object. IIP-0010 already anticipated that new metadata kinds (e.g. view functions) would be added, and proposed handling growth by bumping the struct version: `PackageMetadataV2`, `V3`, and so on (see [IIP-0010 — Backwards Compatibility](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0010/IIP-0010.md#backwards-compatibility)). + +Versioning the struct type for every new metadata kind has two drawbacks: + +1. **Type churn**: every new kind of module metadata forces a new `ModuleMetadata`/`PackageMetadata` struct version, and every reader must learn the new type. A struct field is also strongly typed and ordered, which is a poor fit for an open, growing set of optional metadata kinds. +2. **No partial reads**: an inline `VecMap` is read as a whole. As more kinds are added, readers and tooling that only care about one kind (say, view functions) still materialize the entire module metadata. + +[IIP-0011](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0011/IIP-0011.md) introduces `#[view]` functions and states that view functions are exposed through `PackageMetadata`, "similarly to how authenticator functions are currently exposed", so that clients and tooling can discover them without static analysis. Adding view function metadata is therefore the first concrete case that requires growing the per-module metadata, and it is the trigger for this refinement. + +This proposal addresses both drawbacks by representing module metadata as **dynamic fields keyed by metadata kind**. New kinds are added by writing a new key; readers fetch only the keys they need; and the `PackageMetadata` object type stays stable. + +## Specification + +This proposal modifies only the **publish/upgrade object-creation phase** and the **runtime accessors** of the Package Metadata model. The compilation phase, the trust model, and the package-metadata object id derivation are unchanged from [IIP-0010](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0010/IIP-0010.md): metadata is still extracted from verified bytecode by the protocol, the object is still frozen, and its id is still derived from the package storage id. + +### Layout + +When the relevant feature flag is enabled (see [Activation](#activation)), the protocol builds `PackageMetadataV1` with the following dynamic-field layout instead of the inline `modules_metadata` map. + +The `PackageMetadataV1` object carries two dynamic fields: + +1. A **metadata-layout version** marker under the `PackageMetadataVersionFieldName` key, set to `2`. Its presence and value distinguish the dynamic-field layout from the legacy inline layout. +2. A **modules map** under the `ModulesMetadataFieldName` key, of type `VecMap`, mapping each module name to its `ModuleMetadata` object. + +The inline `modules_metadata: VecMap` field of `PackageMetadataV1` remains in the struct definition for backwards compatibility but is left **empty** under this layout; it is deprecated and MUST NOT be read by new code. + +Each `ModuleMetadata` is a standalone object that behaves as a key-value store backed by dynamic fields: + +```move +public struct ModuleMetadata has key, store { + /// the ID of this module_metadata + id: UID, + /// the number of key-value pairs in the module_metadata + size: u64, +} +``` + +Each entry in a `ModuleMetadata` is a dynamic field whose key type identifies a **kind** of metadata and whose value holds that kind's payload. The protocol defines the following keys for this version: + +- `ModuleMetadataV1FieldName` → `ModuleMetadataV1` — the authenticator metadata from IIP-0010, re-homed under this key. Present only for modules that have at least one authenticator. +- `ViewFunctionMetadataV1FieldName` → `vector` — the list of view function names declared in the module (new; see [View function metadata](#view-function-metadata)). Present for every module recorded under this layout. + +Additional kinds can be introduced later as new key types without changing `ModuleMetadata`, `PackageMetadataV1`, or any existing key's payload. + +### Module metadata object id derivation + +Each `ModuleMetadata` object id is derived deterministically from the owning package storage id and the module name, reusing the same derived-object mechanism IIP-0010 uses for the package metadata object id: + +```rust +module_metadata_id += derive_object_id(package_storage_id, <0x2::module_metadata::ModuleMetadataKey>, module_name) +``` + +Where `ModuleMetadataKey` wraps the module name (`ModuleMetadataKey(ascii::String)`). Deriving from the package id together with the module name guarantees that: + +- each module of a package gets a distinct `ModuleMetadata` object (and therefore a distinct dynamic-field namespace), and +- the same module name in different packages does not collide. + +As with the package metadata id, this id can be computed on-chain and by off-chain tooling without a lookup. + +### View function metadata + +A function annotated with `#[view]` ([IIP-0011](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0011/IIP-0011.md)) is recorded in its module's `ModuleMetadata` under the `ViewFunctionMetadataV1FieldName` key as a `vector` of function names. + +The flow mirrors the authenticator flow of IIP-0010: + +1. **Compilation**: the `#[view]` attribute is recorded in the function's `RuntimeModuleMetadata` and embedded in the module bytecode, exactly as other recognized attributes are. +2. **Publish/upgrade verification**: the IOTA bytecode verifier validates each function marked as a view function against the IIP-0011 signature constraints. A package that marks a non-conforming function as `#[view]` fails to publish — this is what lets readers trust the recorded list without re-checking signatures. +3. **Object creation**: for each module, the protocol writes the verified view function names into the module's `ModuleMetadata`. + +Because the recorded list is protocol-attested, a reader can treat membership in the list as a trusted "this function satisfies the view constraints" claim. + +### Conditional creation + +The conditional-creation rule from IIP-0010 (a `PackageMetadata` object is created only when meaningful metadata exists) is preserved and broadened to the new kinds: under this layout, a `PackageMetadataV1` is created when any module of the package contributes at least one kind of metadata (an authenticator **or** a view function). Modules with no recognized metadata are not recorded. + +### Activation + +The new layout is gated behind the `package_metadata_with_dynamic_module_metadata` protocol feature flag. The flag depends on the existing package-metadata publishing flag: enabling it requires package-metadata publishing to be enabled. + +- When the flag is **off**, the protocol builds the legacy inline layout from IIP-0010 (no version marker, no dynamic fields). +- When the flag is **on**, the protocol builds the dynamic-field layout described here. + +This lets the network adopt the new layout at a protocol upgrade boundary while existing metadata objects remain valid. + +### Move types and methods + +#### New accessors + +```move +/// Borrows the `ModuleMetadata` of the module named `module_name`. +/// Aborts with `EModuleMetadataNotFound` if the package has no metadata for that module. +public fun module_metadata( + self: &PackageMetadataV1, + module_name: &ascii::String, +): &ModuleMetadata; + +/// Borrows the `AuthenticatorMetadataV1` of `function_name` in `module_name`. +public fun module_authenticator_function_metadata_v1( + self: &PackageMetadataV1, + module_name: &ascii::String, + function_name: &ascii::String, +): &AuthenticatorMetadataV1; + +/// Borrows / safely gets the `AuthenticatorMetadataV1` of `function_name` +/// from a given module metadata. +public fun authenticator_function_metadata_v1( + self: &ModuleMetadata, + function_name: &ascii::String, +): &AuthenticatorMetadataV1; +public fun try_get_authenticator_function_metadata_v1( + self: &ModuleMetadata, + function_name: &ascii::String, +): Option; +``` + +On the `ModuleMetadata` object: + +```move +/// Returns true iff the module recorded any view function metadata. +public fun contains_view_functions_metadata_v1(self: &ModuleMetadata): bool; + +/// Borrows the list of view function names recorded for the module. +public fun borrow_view_functions_metadata_v1(self: &ModuleMetadata): &vector; + +/// Returns true iff `function_name` is one of the module's view functions. +public fun is_view_function_v1(self: &ModuleMetadata, function_name: &ascii::String): bool; + +/// Number of metadata kinds recorded for the module / whether it is empty. +public fun length(self: &ModuleMetadata): u64; +public fun is_empty(self: &ModuleMetadata): bool; +``` + +#### Deprecated accessors + +The IIP-0010 accessors that return `ModuleMetadataV1` by value are retained but **deprecated**. They are made layout-aware: when called on an object built with the dynamic-field layout (version marker `== 2`), they read through the dynamic fields; otherwise they fall back to the inline map. This keeps existing callers working across both layouts. + +```move +#[deprecated] +public fun try_get_modules_metadata_v1( + self: &PackageMetadataV1, + module_name: &ascii::String, +): Option; + +#[deprecated] +public fun modules_metadata_v1( + self: &PackageMetadataV1, + module_name: &ascii::String, +): &ModuleMetadataV1; +``` + +## Rationale + +**Why dynamic fields instead of struct versioning.** IIP-0010 proposed accommodating new metadata kinds by bumping the `PackageMetadata`/`ModuleMetadata` struct version. Dynamic fields keyed by metadata kind achieve the same extensibility without type churn: a new kind is a new key, not a new struct type, and the `PackageMetadata` object type stays stable across additions. Readers fetch only the kinds they care about, and unknown kinds are simply absent keys rather than version mismatches. This matches how the metadata is actually consumed — an open, optional, per-module set of facts — better than a fixed, ordered struct. + +**Why a version marker.** The `PackageMetadataVersionFieldName` field (value `2`) lets a reader, and the deprecated accessors, distinguish the dynamic-field layout from the legacy inline layout on an existing object, so both can coexist on-chain after the protocol upgrade. + +**Why per-module derived objects.** Giving each module its own `ModuleMetadata` object, with an id derived from the package id and module name, keeps each module's dynamic fields in an isolated namespace, makes the module metadata object addressable and computable off-chain, and avoids collisions between identically named modules in different packages. + +**Trust model unchanged.** As in IIP-0010, all content is written by the protocol from verified bytecode and frozen. For view functions specifically, the verifier rejects any package that marks a non-conforming function `#[view]`, so the recorded view function list is trustworthy without re-validation by readers. + +## Backwards Compatibility + +This proposal is backwards compatible. + +1. **Existing metadata objects**: packages published under the IIP-0010 inline layout keep their existing `PackageMetadataV1` objects unchanged. They have no version marker and no dynamic fields, and the deprecated accessors continue to read them via the inline map. +2. **Protocol gating**: the new layout only takes effect once the `package_metadata_with_dynamic_module_metadata` flag is enabled at a protocol upgrade. Before activation, behavior is identical to IIP-0010. +3. **Accessor compatibility**: the IIP-0010 accessors (`modules_metadata_v1`, `try_get_modules_metadata_v1`) remain available and are made layout-aware, so callers compiled against IIP-0010 continue to work against objects in either layout. They are deprecated in favor of the new `module_metadata` / `*_function_metadata_v1` accessors. +4. **Adding future kinds**: new module metadata kinds can be added as additional dynamic-field keys without changing any existing type or payload, and without bumping the layout version. + +The `modules_metadata` inline field of `PackageMetadataV1` is retained for compatibility but is empty under the new layout and deprecated for reads. + +## Test Cases + +1. Authenticator metadata round-trips through the dynamic-field layout (publish a package with `#[authenticator]` functions; read back via both the new and the deprecated accessors). +2. View function metadata is recorded for `#[view]` functions and queryable via `is_view_function_v1` / `borrow_view_functions_metadata_v1`. +3. Conditional creation: a package with neither authenticators nor view functions produces no `PackageMetadata` object; a package with only view functions does. +4. A package upgrade that removes a view function produces an updated metadata object for the new version while the previous version's metadata stays valid. +5. Deprecated accessors behave identically against legacy inline-layout objects and dynamic-field-layout objects. +6. The bytecode verifier rejects a package that marks a non-conforming function as `#[view]` at publish time. + +## Reference Implementation + +Implemented in the IOTA core monorepo. Key components: + +- Framework modules `iota::package_metadata` and `iota::module_metadata` (`crates/iota-framework/packages/iota-framework/sources/package_metadata/`). +- Protocol-side object creation under the feature flag (`iota-execution/latest/iota-adapter/.../package_metadata.rs`). +- View function bytecode verification (`iota-execution/latest/iota-verifier/.../view_function_verifier.rs`). +- Feature flag `package_metadata_with_dynamic_module_metadata` in `crates/iota-protocol-config`. + +See also [IIP-0010](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0010/IIP-0010.md) (Package Metadata) and [IIP-0011](https://github.com/iotaledger/IIPs/blob/main/iips/IIP-0011/IIP-0011.md) (Core Move View Functions). + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).