Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
- Explicitly use `get_native_account_active_storage_slots_ptr` in `account::set_item` and `account::set_map_item`.
- Added Ownable2Step as an Account Component ([#2572](https://github.com/0xMiden/protocol/pull/2572))
- [BREAKING] Introduced `PrivateNoteHeader` for output notes and removed `RawOutputNote::Header` variant ([#2569](https://github.com/0xMiden/protocol/pull/2569)).
- Enforce consistency between `AccountComponentMetadata` names and the account component's library path ([#2593](https://github.com/0xMiden/protocol/pull/2593)).

## 0.13.3 (2026-01-27)

Expand Down
12 changes: 11 additions & 1 deletion crates/miden-agglayer/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ASM_NOTE_SCRIPTS_DIR: &str = "note_scripts";
const ASM_AGGLAYER_DIR: &str = "agglayer";
const ASM_AGGLAYER_BRIDGE_DIR: &str = "agglayer/bridge";
const ASM_COMPONENTS_DIR: &str = "components";
const AGGLAYER_COMPONENTS_NAMESPACE: &str = "agglayer::components";

const AGGLAYER_ERRORS_FILE: &str = "src/errors/agglayer.rs";
const AGGLAYER_ERRORS_ARRAY_NAME: &str = "AGGLAYER_ERRORS";
Expand Down Expand Up @@ -178,7 +179,16 @@ fn compile_account_components(
let component_source_code = fs::read_to_string(&masm_file_path)
.expect("reading the component's MASM source code should succeed");

let named_source = NamedSource::new(component_name.clone(), component_source_code);
let relative_path = masm_file_path
.strip_prefix(source_dir)
.expect("masm file should be inside source dir");
let mut library_path = AGGLAYER_COMPONENTS_NAMESPACE.to_owned();
for component in relative_path.with_extension("").components() {
let part = component.as_os_str().to_str().expect("valid UTF-8");
library_path.push_str("::");
library_path.push_str(part);
}
let named_source = NamedSource::new(library_path, component_source_code);

let component_library = assembler
.clone()
Expand Down
13 changes: 10 additions & 3 deletions crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ fn agglayer_faucet_component_library() -> Library {
/// Creates an AggLayer Bridge component with the specified storage slots.
fn bridge_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = agglayer_bridge_component_library();
let metadata = AccountComponentMetadata::new("agglayer::bridge", AccountType::all())
let metadata = AccountComponentMetadata::new(AggLayerBridge::NAME, AccountType::all())
.with_description("Bridge component for AggLayer");

AccountComponent::new(library, storage_slots, metadata)
Expand Down Expand Up @@ -172,6 +172,9 @@ pub struct AggLayerBridge {
}

impl AggLayerBridge {
/// The name of the component.
pub const NAME: &str = "agglayer::components::bridge";

/// Creates a new AggLayer bridge component with the standard configuration.
pub fn new(bridge_admin_id: AccountId, ger_manager_id: AccountId) -> Self {
Self { bridge_admin_id, ger_manager_id }
Expand Down Expand Up @@ -244,8 +247,9 @@ impl From<AggLayerBridge> for AccountComponent {
/// validates CLAIM notes against a bridge MMR account before minting assets.
fn agglayer_faucet_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = agglayer_faucet_component_library();
let metadata = AccountComponentMetadata::new("agglayer::faucet", [AccountType::FungibleFaucet])
.with_description("AggLayer faucet component with bridge validation");
let metadata =
AccountComponentMetadata::new(AggLayerFaucet::NAME, [AccountType::FungibleFaucet])
.with_description("AggLayer faucet component with bridge validation");

AccountComponent::new(library, storage_slots, metadata).expect(
"agglayer_faucet component should satisfy the requirements of a valid account component",
Expand Down Expand Up @@ -328,6 +332,9 @@ pub struct AggLayerFaucet {
}

impl AggLayerFaucet {
/// The name of the component.
pub const NAME: &str = "agglayer::components::faucet";

/// Creates a new AggLayer faucet component from the given configuration.
///
/// # Errors
Expand Down
12 changes: 8 additions & 4 deletions crates/miden-protocol/src/account/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ mod tests {
use std::sync::LazyLock;

use assert_matches::assert_matches;
use miden_assembly::diagnostics::NamedSource;
use miden_assembly::{Assembler, Library};
use miden_core::mast::MastNodeExt;

Expand All @@ -300,6 +301,9 @@ mod tests {
use crate::account::{AccountProcedureRoot, StorageSlot, StorageSlotName};
use crate::testing::noop_auth_component::NoopAuthComponent;

const CUSTOM_COMPONENT1_NAME: &str = "test::custom_component1";
const CUSTOM_COMPONENT2_NAME: &str = "test::custom_component2";

const CUSTOM_CODE1: &str = "
pub proc foo
push.2.2 add eq.4
Expand All @@ -313,12 +317,12 @@ mod tests {

static CUSTOM_LIBRARY1: LazyLock<Library> = LazyLock::new(|| {
Assembler::default()
.assemble_library([CUSTOM_CODE1])
.assemble_library([NamedSource::new(CUSTOM_COMPONENT1_NAME, CUSTOM_CODE1)])
.expect("code should be valid")
});
static CUSTOM_LIBRARY2: LazyLock<Library> = LazyLock::new(|| {
Assembler::default()
.assemble_library([CUSTOM_CODE2])
.assemble_library([NamedSource::new(CUSTOM_COMPONENT2_NAME, CUSTOM_CODE2)])
.expect("code should be valid")
});

Expand All @@ -344,7 +348,7 @@ mod tests {
value[0] = Felt::new(custom.slot0);

let metadata =
AccountComponentMetadata::new("test::custom_component1", AccountType::all());
AccountComponentMetadata::new(CUSTOM_COMPONENT1_NAME, AccountType::all());
AccountComponent::new(
CUSTOM_LIBRARY1.clone(),
vec![StorageSlot::with_value(CUSTOM_COMPONENT1_SLOT_NAME.clone(), value)],
Expand All @@ -366,7 +370,7 @@ mod tests {
value1[3] = Felt::new(custom.slot1);

let metadata =
AccountComponentMetadata::new("test::custom_component2", AccountType::all());
AccountComponentMetadata::new(CUSTOM_COMPONENT2_NAME, AccountType::all());
AccountComponent::new(
CUSTOM_LIBRARY2.clone(),
vec![
Expand Down
14 changes: 10 additions & 4 deletions crates/miden-protocol/src/account/code/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ mod tests {

use assert_matches::assert_matches;
use miden_assembly::Assembler;
use miden_assembly::diagnostics::NamedSource;

use super::{AccountCode, Deserializable, Serializable};
use crate::account::code::build_procedure_commitment;
Expand Down Expand Up @@ -447,8 +448,10 @@ mod tests {

#[test]
fn test_account_code_no_auth_component() {
let library = Assembler::default().assemble_library([CODE]).unwrap();
let metadata = AccountComponentMetadata::new("test::no_auth", AccountType::all());
let name = "test::no_auth";
let library =
Assembler::default().assemble_library([NamedSource::new(name, CODE)]).unwrap();
let metadata = AccountComponentMetadata::new(name, AccountType::all());
let component = AccountComponent::new(library, vec![], metadata).unwrap();

let err =
Expand Down Expand Up @@ -485,8 +488,11 @@ mod tests {
end
";

let library = Assembler::default().assemble_library([code_with_multiple_auth]).unwrap();
let metadata = AccountComponentMetadata::new("test::multiple_auth", AccountType::all());
let name = "test::multiple_auth";
let library = Assembler::default()
.assemble_library([NamedSource::new(name, code_with_multiple_auth)])
.unwrap();
let metadata = AccountComponentMetadata::new(name, AccountType::all());
let component = AccountComponent::new(library, vec![], metadata).unwrap();

let err =
Expand Down
79 changes: 64 additions & 15 deletions crates/miden-protocol/src/account/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ impl AccountComponent {
///
/// Returns an error if:
/// - The number of given [`StorageSlot`]s exceeds 255.
/// - The metadata name is not a valid path.
/// - The metadata name is not a prefix (at a `::` boundary) of any library export path.
pub fn new(
code: impl Into<AccountComponentCode>,
storage_slots: Vec<StorageSlot>,
Expand All @@ -73,11 +75,28 @@ impl AccountComponent {
u8::try_from(storage_slots.len())
.map_err(|_| AccountError::StorageTooManySlots(storage_slots.len() as u64))?;

Ok(Self {
code: code.into(),
storage_slots,
metadata,
})
let code: AccountComponentCode = code.into();
let canonical_name = Path::validate(metadata.name())
.map_err(|err| {
AccountError::other_with_source("account component name is not a valid path", err)
})?
.canonicalize()
.expect("Path::validate should ensure valid path");

for export in code.as_library().exports() {
// Contrary to starts_with_exactly, this ignores whether paths start with `::` or not.
// That allows a component's name to start without `::` even if the library's path
// starts with `::`.
let is_prefix = export.path().starts_with(canonical_name.as_path());
if !is_prefix {
return Err(AccountError::AccountComponentNameMismatch {
name: metadata.name().into(),
export_path: export.path().as_str().into(),
});
}
}

Ok(Self { code, storage_slots, metadata })
}

/// Creates an [`AccountComponent`] from a [`Package`] using [`InitStorageData`].
Expand Down Expand Up @@ -235,7 +254,9 @@ mod tests {
use alloc::string::ToString;
use alloc::sync::Arc;

use assert_matches::assert_matches;
use miden_assembly::Assembler;
use miden_assembly::diagnostics::NamedSource;
use miden_mast_package::{
MastArtifact,
Package,
Expand All @@ -252,16 +273,16 @@ mod tests {

#[test]
fn test_extract_metadata_from_package() {
let name = "test_component";
// Create a simple library for testing
let library = Assembler::default().assemble_library([CODE]).unwrap();
let library =
Assembler::default().assemble_library([NamedSource::new(name, CODE)]).unwrap();

// Test with metadata
let metadata = AccountComponentMetadata::new(
"test_component",
[AccountType::RegularAccountImmutableCode],
)
.with_description("A test component")
.with_version(Version::new(1, 0, 0));
let metadata =
AccountComponentMetadata::new(name, [AccountType::RegularAccountImmutableCode])
.with_description("A test component")
.with_version(Version::new(1, 0, 0));

let metadata_bytes = metadata.to_bytes();
let package_with_metadata = Package {
Expand All @@ -279,7 +300,7 @@ mod tests {

let extracted_metadata =
AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
assert_eq!(extracted_metadata.name(), "test_component");
assert_eq!(extracted_metadata.name(), name);
assert!(
extracted_metadata
.supported_types()
Expand All @@ -305,12 +326,14 @@ mod tests {

#[test]
fn test_from_library_with_init_data() {
let name = "test_component";
// Create a simple library for testing
let library = Assembler::default().assemble_library([CODE]).unwrap();
let library =
Assembler::default().assemble_library([NamedSource::new(name, CODE)]).unwrap();
let component_code = AccountComponentCode::from(library.clone());

// Create metadata for the component
let metadata = AccountComponentMetadata::new("test_component", AccountType::regular())
let metadata = AccountComponentMetadata::new(name, AccountType::regular())
.with_description("A test component")
.with_version(Version::new(1, 0, 0));

Expand Down Expand Up @@ -342,4 +365,30 @@ mod tests {
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("package does not contain account component metadata"));
}

#[test]
fn test_name_mismatch_returns_error() {
let correct_name = "correct::namespace";
let wrong_name = "wrong::name";

// Create a library under the "correct::namespace" path.
let library = Assembler::default()
.assemble_library([NamedSource::new(correct_name, CODE)])
.unwrap();

// Use a metadata name that does NOT match the library path.
let metadata = AccountComponentMetadata::new(wrong_name, AccountType::all());

let result = AccountComponent::new(library, vec![], metadata);
assert_matches!(
result,
Err(AccountError::AccountComponentNameMismatch { name, export_path }) => {
assert_eq!(name.as_ref(), wrong_name);
assert!(
export_path.contains(correct_name),
"export_path `{export_path}` should contain `{correct_name}`"
);
}
);
}
}
7 changes: 5 additions & 2 deletions crates/miden-protocol/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ mod tests {

use assert_matches::assert_matches;
use miden_assembly::Assembler;
use miden_assembly::diagnostics::NamedSource;
use miden_crypto::utils::{Deserializable, Serializable};
use miden_crypto::{Felt, Word};

Expand Down Expand Up @@ -799,12 +800,14 @@ mod tests {
/// account type returns an error.
#[test]
fn test_account_unsupported_component_type() {
let name = "test::component1";
let code1 = "pub proc foo add end";
let library1 = Assembler::default().assemble_library([code1]).unwrap();
let library1 =
Assembler::default().assemble_library([NamedSource::new(name, code1)]).unwrap();

// This component support all account types except the regular account with updatable code.
let metadata = AccountComponentMetadata::new(
"test::component1",
name,
[
AccountType::FungibleFaucet,
AccountType::NonFungibleFaucet,
Expand Down
4 changes: 4 additions & 0 deletions crates/miden-protocol/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ pub enum AccountError {
AccountComponentAssemblyError(Report),
#[error("failed to merge components into one account code mast forest")]
AccountComponentMastForestMergeError(#[source] MastForestError),
#[error(
"account component metadata name `{name}` is not a prefix of library export path `{export_path}`"
)]
AccountComponentNameMismatch { name: Box<str>, export_path: Box<str> },
#[error("account component contains multiple authentication procedures")]
AccountComponentMultipleAuthProcedures,
#[error("failed to update asset vault")]
Expand Down
6 changes: 4 additions & 2 deletions crates/miden-protocol/src/testing/account_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// ================================================================================================

use miden_assembly::Assembler;
use miden_assembly::diagnostics::NamedSource;

use crate::account::component::AccountComponentMetadata;
use crate::account::{AccountCode, AccountComponent, AccountType};
Expand All @@ -20,10 +21,11 @@ pub const CODE: &str = "
impl AccountCode {
/// Creates a mock [AccountCode] with default assembler and mock code
pub fn mock() -> AccountCode {
let name = "miden::testing::mock";
let library = Assembler::default()
.assemble_library([CODE])
.assemble_library([NamedSource::new(name, CODE)])
.expect("mock account component should assemble");
let metadata = AccountComponentMetadata::new("miden::testing::mock", AccountType::all());
let metadata = AccountComponentMetadata::new(name, AccountType::all());
let component = AccountComponent::new(library, vec![], metadata).unwrap();

Self::from_components(
Expand Down
9 changes: 7 additions & 2 deletions crates/miden-protocol/src/testing/add_component.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::account::component::AccountComponentMetadata;
use crate::account::{AccountComponent, AccountType};
use crate::assembly::diagnostics::NamedSource;
use crate::assembly::{Assembler, Library};
use crate::utils::sync::LazyLock;

Expand All @@ -14,7 +15,7 @@ const ADD_CODE: &str = "

static ADD_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
Assembler::default()
.assemble_library([ADD_CODE])
.assemble_library([NamedSource::new(AddComponent::NAME, ADD_CODE)])
.expect("add code should be valid")
});

Expand All @@ -23,9 +24,13 @@ static ADD_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
/// The component defines an `add5` procedure that adds 5 to its input.
pub struct AddComponent;

impl AddComponent {
pub const NAME: &str = "miden::testing::add";
}

impl From<AddComponent> for AccountComponent {
fn from(_: AddComponent) -> Self {
let metadata = AccountComponentMetadata::new("miden::testing::add", AccountType::all())
let metadata = AccountComponentMetadata::new(AddComponent::NAME, AccountType::all())
.with_description("Add component for testing");

AccountComponent::new(ADD_LIBRARY.clone(), vec![], metadata)
Expand Down
Loading
Loading