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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@
- **Commit authorship:** Always commit as Claude, not as the user. Use: `git -c user.name="Claude (Opus)" -c user.email="noreply@anthropic.com" -c commit.gpgsign=false commit -m "message"`
- **Commit frequency:** Always commit at the end of each task. Avoid single commits that span multiple unrelated changes.
- **Responding to PR review:** When addressing review feedback on a pushed PR, add new commits on top of the branch (e.g. `fix: address review comments`). NEVER amend, squash, or otherwise rewrite already-pushed commits to incorporate review changes, and NEVER force-push the branch to do so - this destroys the diff reviewers rely on to see what changed since their review. The only time a force-push is acceptable is when the branch must be rebased onto an updated base (or a PR lower in a stack changed); that is a base update, not a review response, and should be called out explicitly.
- **Never post to GitHub unprompted:** Never run `gh pr comment`, `gh pr review`, `gh issue comment`, or `git push` unless explicitly asked in that message. When asked to draft review text, output it in chat for the user to paste - never post it.

## Scope Discipline

- Implement the minimum that satisfies the request. Do not add helper abstractions, config knobs, extra bounds/limits, fallback lookups, or "while I was here" refactors.
- Prefer stdlib primitives over hand-rolled equivalents (e.g. `Read::take` over a custom limited reader).
- If extra work seems warranted, list it as a suggestion at the end instead of implementing it.

## Explain vs. Act

- When asked "why", "how does X work", or "explain", answer read-only: use Read/Grep only, make no edits and run no build commands unless asked.
- Cite file:line evidence for each claim rather than summarizing from memory.
- When told code has changed outside the session, re-read the files from disk instead of relying on earlier copies.

## Build & Formatting

- Formatting must pass nightly rustfmt: run `make format` (i.e. `cargo +nightly fmt --all`) before claiming work is done. Non-nightly formatting fails CI on import ordering.

## Output Formatting

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
- [BREAKING] Added the `miden::standards::expiration` MASM module with `apply_default` and used it to apply a default 20-block transaction expiration limit to the standard allowlist and blocklist transfer policies and the fee manager's `estimate_note_fee` procedure ([#3512](https://github.com/0xMiden/protocol/pull/3512)).
- [BREAKING] Extracted the shared `MastForestScript` type and `MastForestScriptError` backing `NoteScript` / `TransactionScript`, moving `TransactionScript` into `transaction::script` ([#3516](https://github.com/0xMiden/protocol/pull/3516)).
- Documented the RBAC freeze-only actor pattern on `Authority` and added test coverage pinning that a `FREEZER` can trip the emergency switch but can never unfreeze the account ([#3520](https://github.com/0xMiden/protocol/pull/3520)).
- [BREAKING] Moved account component metadata from Rust into a `[package.metadata.account-component]` table in each component's `miden-project.toml`, embedding it into the compiled `.masp` package and removing the now-redundant `*_slot_schema` accessors ([#3587](https://github.com/0xMiden/protocol/pull/3587)).
- [BREAKING] Replaced the dedicated AggLayer faucet account component with the standard `FungibleFaucet`. `AggLayerFaucet` is now a stateless namespace, `AgglayerFaucetError` and the `miden-agglayer-faucet` MASM package were removed, `AggLayerFaucet::account_builder` and `create_existing_agglayer_faucet` take a token name, and `account_builder` now takes `TokenName` / `TokenSymbol` / `AssetAmount` rather than `&str` / `Felt` ([#3525](https://github.com/0xMiden/protocol/pull/3525)).
- [BREAKING] Updated `NoteScript::from_package` and `TransactionScript::from_package` to reject executable packages with the new `MastForestScriptError::ExecutablePackage`, so scripts are identified only by their `@note_script` / `@transaction_script` attribute ([#3528](https://github.com/0xMiden/protocol/pull/3528)).
- [BREAKING] Updated `AccountComponent::from_package` to take `Package` by value ([#3528](https://github.com/0xMiden/protocol/pull/3528)).
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,6 @@ serde_json = { default-features = false, version = "1.0" }
syn = { features = ["full"], version = "2" }
thiserror = { default-features = false, version = "2.0" }
tokio = { default-features = false, features = ["sync"], version = "1" }
toml = { version = "1.0" }
tonic = { default-features = false, version = "0.14" }
walkdir = { version = "2.5" }
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ miden-agglayer.workspace = true
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "AggLayer bridge"
name = "agglayer::bridge"
version = "1.0.0"
2 changes: 2 additions & 0 deletions crates/miden-agglayer/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use miden_protocol::ProtocolLib;
use miden_protocol::account::{AccountCode, AccountComponent, AccountComponentMetadata};
use miden_protocol::note::NoteScriptRoot;
use miden_protocol::transaction::TransactionKernel;
use miden_protocol::utils::serde::Serializable;
use miden_protocol_build_utils::{
ErrorModule,
PROJECT_MANIFEST,
Expand Down Expand Up @@ -85,6 +86,7 @@ fn main() -> Result<()> {
source_dir.join(ASM_COMPONENTS_DIR).join(PROJECT_MANIFEST),
&mut registry,
&target_dir.join(ASM_COMPONENTS_DIR),
|metadata| Ok(AccountComponentMetadata::from_toml(metadata).into_diagnostic()?.to_bytes()),
)?;

// generate agglayer specific constants
Expand Down
4 changes: 2 additions & 2 deletions crates/miden-agglayer/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,8 +864,8 @@ pub enum AgglayerBridgeError {
/// Creates an AggLayer Bridge component with the specified storage slots.
fn bridge_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let package = agglayer_bridge_component_package();
let metadata = AccountComponentMetadata::new("agglayer::bridge")
.with_description("Bridge component for AggLayer");
let metadata = AccountComponentMetadata::try_from(&package)
.expect("shipped bridge package should declare account component metadata");

AccountComponent::new(package, storage_slots, metadata)
.expect("bridge component should satisfy the requirements of a valid account component")
Expand Down
1 change: 1 addition & 0 deletions crates/miden-protocol-build-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ miden-mast-package = { features = ["std"], workspace = true }
miden-package-registry = { features = ["std"], workspace = true }
miden-project = { features = ["serde", "std"], workspace = true }
regex = { workspace = true }
toml = { workspace = true }
walkdir = { workspace = true }
43 changes: 41 additions & 2 deletions crates/miden-protocol-build-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use miden_assembly::debuginfo::{DefaultSourceManager, SourceManager, SourceManag
use miden_assembly::diagnostics::{IntoDiagnostic, Result};
use miden_assembly::{Assembler, ProjectTargetSelector, Report};
use miden_core::events::EventId;
use miden_mast_package::Package;
use miden_mast_package::{Package, Section, SectionId};
use miden_package_registry::InMemoryPackageRegistry;
use miden_project::Workspace;
use regex::Regex;
Expand All @@ -32,6 +32,10 @@ pub const PROJECT_MANIFEST: &str = "miden-project.toml";
/// information; consumers can strip it as needed.
pub const BUILD_PROFILE: &str = "dev";

/// Name of the custom manifest table declaring the metadata of an account component, i.e.
/// `[package.metadata.account-component]`.
const COMPONENT_METADATA_TABLE: &str = "account-component";

// PACKAGE ASSEMBLY HELPERS
// ================================================================================================

Expand All @@ -57,10 +61,16 @@ pub fn assemble_project(
/// library target into a library package, writes each package to `target_dir` as a `.masp` file,
/// and returns the assembled packages. Members without a library target are skipped. Dependencies
/// are resolved against `registry`.
///
/// If a member declares a `[package.metadata.account-component]` manifest table, the table is
/// passed to `encode_account_component_metadata` as a TOML document and the returned bytes are
/// embedded in the package as its [`SectionId::ACCOUNT_COMPONENT_METADATA`] section. Members
/// without the table are assembled as-is.
pub fn assemble_workspace(
manifest_path: impl AsRef<Path>,
registry: &mut InMemoryPackageRegistry,
target_dir: &Path,
encode_account_component_metadata: fn(&str) -> Result<Vec<u8>>,
) -> Result<Vec<Arc<Package>>> {
let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
let manifest = source_manager.load_file(manifest_path.as_ref()).into_diagnostic()?;
Expand All @@ -76,17 +86,46 @@ pub fn assemble_workspace(
continue;
}

let package = assembler
let mut package = assembler
.clone()
.for_project(member.clone(), registry)?
.assemble(ProjectTargetSelector::Library, BUILD_PROFILE)?;

if let Some(metadata) = component_metadata_toml(member)? {
let section = Section::new(
SectionId::ACCOUNT_COMPONENT_METADATA,
encode_account_component_metadata(&metadata)?,
);
Arc::make_mut(&mut package).sections.push(section);
}

package.write_masp_file(target_dir).into_diagnostic()?;
packages.push(package);
}

Ok(packages)
}

/// Returns the `[package.metadata.account-component]` table of `package`'s manifest as a standalone
/// TOML document, or `None` if the manifest does not declare one.
fn component_metadata_toml(package: &miden_project::Package) -> Result<Option<String>> {
let table = package.metadata().iter().find_map(|(name, table)| {
let name: &str = name;
(name == COMPONENT_METADATA_TABLE).then_some(table)
});
let Some(table) = table else {
return Ok(None);
};

let mut document = toml::Table::new();
for (key, value) in table.iter() {
let key: &str = key;
document.insert(key.to_string(), (**value).clone());
}

toml::to_string(&document).into_diagnostic().map(Some)
}

// ERROR CONSTANTS EXTRACTION
// ================================================================================================

Expand Down
2 changes: 1 addition & 1 deletion crates/miden-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ rand_xoshiro = { default-features = false, optional = true, version = "0.8" }
semver = { features = ["serde"], version = "1.0" }
serde = { features = ["derive"], optional = true, workspace = true }
thiserror = { workspace = true }
toml = { optional = true, version = "1.0" }
toml = { optional = true, workspace = true }
# for SecretKey generation
rand_chacha = { optional = true, workspace = true }

Expand Down
2 changes: 1 addition & 1 deletion crates/miden-standards/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ thiserror = { workspace = true }
miden-assembly = { features = ["std"], workspace = true }
miden-core-lib = { workspace = true }
miden-package-registry = { features = ["resolver", "std"], workspace = true }
miden-protocol = { workspace = true }
miden-protocol = { features = ["std"], workspace = true }
miden-protocol-build-utils = { workspace = true }

[dev-dependencies]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,32 @@ path = "authority.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Account-wide authority gating state-mutating operations behind auth-only, owner-based, or RBAC checks"
name = "miden::standards::access::authority"
version = "1.0.0"

[[package.metadata.account-component.storage.slots]]
description = "Per-procedure role assignment (procedure root -> role symbol)"
name = "miden::standards::access::authority::procedure_roles"
type = { key = "word", value = "miden::standards::access::role_symbol" }

[[package.metadata.account-component.storage.slots]]
description = "Authority configuration"
name = "miden::standards::access::authority::authority_config"

# Word layout: [authority, is_frozen, 0, 0]
[[package.metadata.account-component.storage.slots.type]]
name = "authority"
type = "u8"

[[package.metadata.account-component.storage.slots.type]]
name = "is_frozen"
type = "u8"

[[package.metadata.account-component.storage.slots.type]]
type = "void"

[[package.metadata.account-component.storage.slots.type]]
type = "void"
Comment thread
PhilippGackstatter marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,25 @@ path = "ownable2step.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Two-step ownership management"
name = "miden::standards::access::ownable2step"
version = "1.0.0"

[[package.metadata.account-component.storage.slots]]
description = "Ownership data (owner and nominated owner)"
name = "miden::standards::access::ownable2step::owner_config"

# Word layout: [owner_suffix, owner_prefix, nominated_suffix, nominated_prefix]
[[package.metadata.account-component.storage.slots.type]]
name = "owner_suffix"

[[package.metadata.account-component.storage.slots.type]]
name = "owner_prefix"

[[package.metadata.account-component.storage.slots.type]]
name = "nominated_suffix"

[[package.metadata.account-component.storage.slots.type]]
name = "nominated_prefix"
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ path = "pausable.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Pause switch for blocking the account's state-mutating operations"
name = "miden::standards::access::pausable"
version = "1.0.0"

[[package.metadata.account-component.storage.slots]]
default-value = "false"
description = "Whether the account is paused"
name = "miden::standards::access::pausable::is_paused"
type = "bool"
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ path = "pausable_manager.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Authority-gated pause / unpause admin procedures; requires the Pausable and Authority companion components"
name = "miden::standards::access::pausable::manager"
version = "1.0.0"
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,18 @@ path = "rbac.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Role-based access control"
name = "miden::standards::access::rbac"
version = "1.0.0"

[[package.metadata.account-component.storage.slots]]
description = "Per-role RBAC configuration (member count and delegated admin role)"
name = "miden::standards::access::rbac::role_config"
type = { key = "miden::standards::access::role_symbol", value = "word" }

[[package.metadata.account-component.storage.slots]]
description = "Role membership flag indexed by role symbol and account ID"
name = "miden::standards::access::rbac::role_membership"
type = { key = "word", value = "bool" }
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,57 @@ path = "guarded_multisig.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Multisig authentication with hybrid signature schemes, guarded by a state guardian"
name = "miden::standards::auth::guarded_multisig"
version = "1.0.0"

[[package.metadata.account-component.storage.slots]]
description = "Guardian public key"
name = "miden::standards::auth::guardian::pub_key"
type = { key = "u32", value = "miden::standards::auth::pub_key" }

[[package.metadata.account-component.storage.slots]]
description = "Executed transactions"
name = "miden::standards::auth::multisig::executed_transactions"
Comment thread
bobbinth marked this conversation as resolved.
type = { key = "word", value = "bool" }

[[package.metadata.account-component.storage.slots]]
description = "Approver public keys"
name = "miden::standards::auth::multisig::approver_public_keys"
type = { key = "u32", value = "miden::standards::auth::pub_key" }

[[package.metadata.account-component.storage.slots]]
description = "Procedure thresholds"
name = "miden::standards::auth::multisig::procedure_thresholds"
type = { key = "word", value = "u32" }

[[package.metadata.account-component.storage.slots]]
description = "Guardian scheme ID"
name = "miden::standards::auth::guardian::scheme"
type = { key = "u32", value = "miden::standards::auth::scheme" }

[[package.metadata.account-component.storage.slots]]
description = "Approver scheme IDs"
name = "miden::standards::auth::multisig::approver_schemes"
type = { key = "u32", value = "miden::standards::auth::scheme" }

[[package.metadata.account-component.storage.slots]]
description = "Threshold configuration"
name = "miden::standards::auth::multisig::threshold_config"

# Word layout: [threshold, num_approvers, 0, 0]
[[package.metadata.account-component.storage.slots.type]]
name = "threshold"
type = "u32"

[[package.metadata.account-component.storage.slots.type]]
name = "num_approvers"
type = "u32"

[[package.metadata.account-component.storage.slots.type]]
type = "void"

[[package.metadata.account-component.storage.slots.type]]
type = "void"
Comment thread
TomasArrachea marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,47 @@ path = "multisig.masm"
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true

[package.metadata.account-component]
description = "Multisig authentication using hybrid signature schemes"
name = "miden::standards::auth::multisig"
version = "1.0.0"

[[package.metadata.account-component.storage.slots]]
description = "Executed transactions"
name = "miden::standards::auth::multisig::executed_transactions"
type = { key = "word", value = "bool" }

[[package.metadata.account-component.storage.slots]]
description = "Approver public keys"
name = "miden::standards::auth::multisig::approver_public_keys"
type = { key = "u32", value = "miden::standards::auth::pub_key" }

[[package.metadata.account-component.storage.slots]]
description = "Procedure thresholds"
name = "miden::standards::auth::multisig::procedure_thresholds"
type = { key = "word", value = "u32" }

[[package.metadata.account-component.storage.slots]]
description = "Approver scheme IDs"
name = "miden::standards::auth::multisig::approver_schemes"
type = { key = "u32", value = "miden::standards::auth::scheme" }

[[package.metadata.account-component.storage.slots]]
description = "Threshold configuration"
name = "miden::standards::auth::multisig::threshold_config"

# Word layout: [threshold, num_approvers, 0, 0]
[[package.metadata.account-component.storage.slots.type]]
name = "threshold"
type = "u32"

[[package.metadata.account-component.storage.slots.type]]
name = "num_approvers"
type = "u32"

[[package.metadata.account-component.storage.slots.type]]
type = "void"

[[package.metadata.account-component.storage.slots.type]]
type = "void"
Loading
Loading