use miden_protocol::account::component::{
AccountComponentCode,
AccountComponentMetadata,
SchemaType,
StorageSchema,
StorageSlotSchema,
};
use miden_protocol::account::{
AccountComponent,
AccountProcedureRoot,
StorageSlot,
StorageSlotName,
};
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use super::BasicConstantFeePolicy;
use crate::account::account_component_code;
use crate::procedure_root;
account_component_code!(
CONSTANT_FEE_MANAGER_CODE,
"miden-standards-fees-policies-constant-fee-manager.masp"
);
const CONSTANT_FEE_MANAGER_LIBRARY_PATH: &str =
"miden::standards::components::fees::policies::constant_fee_manager";
procedure_root!(
CONSTANT_FEE_MANAGER_SET_NOTE_FEE,
CONSTANT_FEE_MANAGER_LIBRARY_PATH,
ConstantFeeManager::SET_NOTE_FEE_PROC_NAME,
ConstantFeeManager::code()
);
static FEE_SCHEDULE_SLOT_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new(
"miden::standards::fees::policies::constant_fee_manager::fee_schedule_slot_id",
)
.expect("storage slot name should be valid")
});
#[derive(Debug, Clone)]
pub struct ConstantFeeManager {
fee_schedule_slot: StorageSlotName,
}
impl ConstantFeeManager {
pub const NAME: &'static str = "miden::standards::fees::policies::constant_fee_manager";
const SET_NOTE_FEE_PROC_NAME: &'static str = "set_note_fee";
pub fn new(fee_schedule_slot: StorageSlotName) -> Self {
Self { fee_schedule_slot }
}
pub fn for_basic_constant_fee_policy() -> Self {
Self::new(BasicConstantFeePolicy::fee_schedule_slot_name().clone())
}
pub fn code() -> &'static AccountComponentCode {
&CONSTANT_FEE_MANAGER_CODE
}
pub fn set_note_fee_root() -> AccountProcedureRoot {
*CONSTANT_FEE_MANAGER_SET_NOTE_FEE
}
pub fn fee_schedule_slot_id_slot_name() -> &'static StorageSlotName {
&FEE_SCHEDULE_SLOT_ID_SLOT_NAME
}
pub fn fee_schedule_slot(&self) -> &StorageSlotName {
&self.fee_schedule_slot
}
pub fn component_metadata() -> AccountComponentMetadata {
let storage_schema = StorageSchema::new([(
FEE_SCHEDULE_SLOT_ID_SLOT_NAME.clone(),
StorageSlotSchema::value(
"ID of the fee schedule slot this manager updates",
SchemaType::native_word(),
),
)])
.expect("storage schema should be valid");
AccountComponentMetadata::new(Self::NAME)
.with_description(
"Authority-gated constant-fee schedule admin: exposes `set_note_fee` to update the \
fee schedule slot recorded in its storage, gated by the account-wide Authority \
component.",
)
.with_storage_schema(storage_schema)
}
}
impl From<ConstantFeeManager> for AccountComponent {
fn from(manager: ConstantFeeManager) -> Self {
let id = manager.fee_schedule_slot.id();
let slot_id_word = Word::from([id.suffix(), id.prefix(), Felt::ZERO, Felt::ZERO]);
let slot = StorageSlot::with_value(FEE_SCHEDULE_SLOT_ID_SLOT_NAME.clone(), slot_id_word);
AccountComponent::new(
ConstantFeeManager::code().clone(),
vec![slot],
ConstantFeeManager::component_metadata(),
)
.expect(
"authority-gated constant-fee schedule admin component should satisfy the \
requirements of a valid account component",
)
}
}
#[cfg(test)]
mod tests {
use miden_protocol::account::StorageSlotContent;
use super::*;
#[test]
fn stores_managed_fee_schedule_slot_id() {
let policy_slot = BasicConstantFeePolicy::fee_schedule_slot_name().clone();
let component: AccountComponent = ConstantFeeManager::new(policy_slot.clone()).into();
let slot = component
.storage_slots()
.iter()
.find(|slot| slot.name() == ConstantFeeManager::fee_schedule_slot_id_slot_name())
.expect("manager should declare the fee schedule slot ID slot");
let id = policy_slot.id();
let expected = Word::from([id.suffix(), id.prefix(), Felt::ZERO, Felt::ZERO]);
assert_eq!(slot.content(), &StorageSlotContent::Value(expected));
}
}