extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::string::String;
use miden_processor::crypto::random::RandomCoin;
use miden_protocol::account::{
Account,
AccountBuilder,
AccountId,
AccountProcedureRoot,
AccountType,
AssetCallbackFlag,
RoleSymbol,
};
use miden_protocol::asset::{Asset, AssetAmount, FungibleAsset};
use miden_protocol::note::{Note, NoteTag, NoteType};
use miden_protocol::transaction::RawOutputNote;
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use miden_standards::account::access::pausable::{Pausable, PausableManager, PausableStorage};
use miden_standards::account::access::{AccessControl, Authority};
use miden_standards::account::faucets::{FungibleFaucet, TokenName};
use miden_standards::account::policies::{
BurnPolicy,
MintPolicy,
TokenPolicyManager,
TransferPolicy,
};
use miden_standards::errors::standards::{
ERR_PAUSABLE_IS_PAUSED,
ERR_SENDER_LACKS_ROLE,
ERR_SENDER_NOT_OWNER,
};
use miden_standards::testing::note::NoteBuilder;
use miden_testing::{
AccountState,
Auth,
MockChain,
MockChainBuilder,
assert_transaction_executor_error,
};
pub(crate) static OWNER_ID: LazyLock<AccountId> = LazyLock::new(|| test_account_id(11));
pub(crate) static NON_OWNER_ID: LazyLock<AccountId> = LazyLock::new(|| test_account_id(99));
pub(crate) static ADMIN_ID: LazyLock<AccountId> = LazyLock::new(|| test_account_id(10));
pub(crate) static NON_ADMIN_ID: LazyLock<AccountId> = LazyLock::new(|| test_account_id(98));
pub(crate) fn test_account_id(seed: u8) -> AccountId {
AccountId::builder()
.account_type(AccountType::Private)
.build_with_seed([seed; 32])
}
fn add_faucet_with_pause(
builder: &mut MockChainBuilder,
owner: AccountId,
) -> anyhow::Result<Account> {
let faucet = FungibleFaucet::builder()
.name(TokenName::new("SYM")?)
.symbol("SYM".try_into()?)
.decimals(8)
.max_supply(AssetAmount::new(1_000_000)?)
.build()?;
let account_builder = AccountBuilder::new([43u8; 32])
.account_type(AccountType::Public)
.with_component(faucet)
.with_components(AccessControl::Ownable2Step { owner })
.with_component(Pausable::unpaused())
.with_component(PausableManager);
builder.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)
}
pub(crate) fn build_note(sender: AccountId, code: impl Into<String>) -> anyhow::Result<Note> {
let seed: [u32; 4] = rand::random();
let mut rng = RandomCoin::new(Word::from(seed));
Ok(NoteBuilder::new(sender, &mut rng)
.note_type(NoteType::Private)
.code(code.into())
.build()?)
}
pub(crate) fn build_pause_note(sender: AccountId) -> anyhow::Result<Note> {
build_note(
sender,
r#"
use miden::standards::access::pausable::manager
@note_script
pub proc main
repeat.16 push.0 end
call.manager::pause
dropw dropw dropw dropw
end
"#,
)
}
fn build_unpause_note(sender: AccountId) -> anyhow::Result<Note> {
build_note(
sender,
r#"
use miden::standards::access::pausable::manager
@note_script
pub proc main
repeat.16 push.0 end
call.manager::unpause
dropw dropw dropw dropw
end
"#,
)
}
pub(crate) async fn execute_note_on_faucet(
mock_chain: &mut MockChain,
faucet_id: AccountId,
note: &Note,
) -> anyhow::Result<()> {
let executed = mock_chain
.build_tx_context(faucet_id, &[note.id()], &[])?
.build()?
.execute()
.await?;
mock_chain.add_pending_executed_transaction(&executed)?;
mock_chain.prove_next_block()?;
Ok(())
}
#[tokio::test]
async fn pausable_manager_pause_succeeds_when_sender_is_owner() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause(&mut builder, *OWNER_ID)?;
let pause_note = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
Ok(())
}
#[tokio::test]
async fn pausable_manager_pause_fails_when_sender_not_owner() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause(&mut builder, *OWNER_ID)?;
let attacker_note = build_pause_note(*NON_OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(attacker_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
let result = mock_chain
.build_tx_context(faucet.id(), &[attacker_note.id()], &[])?
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_SENDER_NOT_OWNER);
Ok(())
}
#[tokio::test]
async fn pausable_manager_unpause_fails_when_sender_not_owner() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause(&mut builder, *OWNER_ID)?;
let pause_note = build_pause_note(*OWNER_ID)?;
let attacker_unpause_note = build_unpause_note(*NON_OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
builder.add_output_note(RawOutputNote::Full(attacker_unpause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
let result = mock_chain
.build_tx_context(faucet.id(), &[attacker_unpause_note.id()], &[])?
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_SENDER_NOT_OWNER);
Ok(())
}
#[tokio::test]
async fn pausable_manager_pause_then_unpause_then_pause_again() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause(&mut builder, *OWNER_ID)?;
let pause_note_1 = build_pause_note(*OWNER_ID)?;
let unpause_note = build_unpause_note(*OWNER_ID)?;
let pause_note_2 = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note_1.clone()));
builder.add_output_note(RawOutputNote::Full(unpause_note.clone()));
builder.add_output_note(RawOutputNote::Full(pause_note_2.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note_1).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &unpause_note).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note_2).await?;
Ok(())
}
#[tokio::test]
async fn pausable_manager_unpause_while_unpaused_is_noop() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause(&mut builder, *OWNER_ID)?;
let unpause_note = build_unpause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(unpause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &unpause_note).await?;
Ok(())
}
#[tokio::test]
async fn pausable_manager_pause_while_paused_is_noop() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause(&mut builder, *OWNER_ID)?;
let pause_note_1 = build_pause_note(*OWNER_ID)?;
let pause_note_2 = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note_1.clone()));
builder.add_output_note(RawOutputNote::Full(pause_note_2.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note_1).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note_2).await?;
Ok(())
}
pub(crate) fn role(name: &str) -> RoleSymbol {
RoleSymbol::new(name).expect("role symbol should be valid")
}
fn pause_unpause_roles() -> BTreeMap<AccountProcedureRoot, RoleSymbol> {
BTreeMap::from([
(PausableManager::pause_root(), role("PAUSER")),
(PausableManager::unpause_root(), role("UNPAUSER")),
])
}
fn add_rbac_faucet_with_pause(
builder: &mut MockChainBuilder,
admin: AccountId,
roles: BTreeMap<AccountProcedureRoot, RoleSymbol>,
seed: u8,
mutable_max_supply: bool,
) -> anyhow::Result<Account> {
let faucet = FungibleFaucet::builder()
.name(TokenName::new("SYM")?)
.symbol("SYM".try_into()?)
.decimals(8)
.max_supply(AssetAmount::new(1_000_000)?)
.is_max_supply_mutable(mutable_max_supply)
.build()?;
let account_builder = AccountBuilder::new([seed; 32])
.account_type(AccountType::Public)
.with_component(faucet)
.with_components(AccessControl::Rbac { admin, roles })
.with_component(Pausable::unpaused())
.with_component(PausableManager);
builder.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)
}
pub(crate) fn build_grant_role_note(
sender: AccountId,
role: &RoleSymbol,
account_id: AccountId,
) -> anyhow::Result<Note> {
build_note(
sender,
format!(
r#"
use miden::standards::access::rbac
@note_script
pub proc main
repeat.13 push.0 end
push.{account_prefix}
push.{account_suffix}
push.{role}
call.rbac::grant_role
dropw dropw dropw dropw
end
"#,
account_prefix = account_id.prefix().as_felt(),
account_suffix = account_id.suffix(),
role = Felt::from(role),
),
)
}
pub(crate) fn build_set_max_supply_note(
sender: AccountId,
new_max_supply: u64,
) -> anyhow::Result<Note> {
build_note(
sender,
format!(
r#"
@note_script
pub proc main
push.{new_max_supply}
swap drop
call.::miden::standards::faucets::fungible::set_max_supply
end
"#,
),
)
}
fn is_faucet_paused(mock_chain: &MockChain, faucet_id: AccountId) -> anyhow::Result<bool> {
let account = mock_chain.committed_account(faucet_id)?;
let word = account.storage().get_item(PausableStorage::is_paused_slot())?;
Ok(word != Word::default())
}
#[tokio::test]
async fn rbac_pause_and_unpause_use_distinct_roles() -> anyhow::Result<()> {
let pauser = test_account_id(20);
let unpauser = test_account_id(21);
let admin = *ADMIN_ID;
let mut builder = MockChain::builder();
let faucet = add_rbac_faucet_with_pause(&mut builder, admin, pause_unpause_roles(), 47, false)?;
let grant_pauser = build_grant_role_note(admin, &role("PAUSER"), pauser)?;
let grant_unpauser = build_grant_role_note(admin, &role("UNPAUSER"), unpauser)?;
let pause_note = build_pause_note(pauser)?;
let unpause_note = build_unpause_note(unpauser)?;
for note in [&grant_pauser, &grant_unpauser, &pause_note, &unpause_note] {
builder.add_output_note(RawOutputNote::Full(note.clone()));
}
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &grant_pauser).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &grant_unpauser).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
assert!(is_faucet_paused(&mock_chain, faucet.id())?);
execute_note_on_faucet(&mut mock_chain, faucet.id(), &unpause_note).await?;
assert!(!is_faucet_paused(&mock_chain, faucet.id())?);
Ok(())
}
#[tokio::test]
async fn rbac_pause_fails_when_sender_lacks_pauser_role() -> anyhow::Result<()> {
let unpauser = test_account_id(22);
let admin = *ADMIN_ID;
let mut builder = MockChain::builder();
let faucet = add_rbac_faucet_with_pause(&mut builder, admin, pause_unpause_roles(), 48, false)?;
let grant_unpauser = build_grant_role_note(admin, &role("UNPAUSER"), unpauser)?;
let pause_note = build_pause_note(unpauser)?;
builder.add_output_note(RawOutputNote::Full(grant_unpauser.clone()));
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &grant_unpauser).await?;
let result = mock_chain
.build_tx_context(faucet.id(), &[pause_note.id()], &[])?
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_SENDER_LACKS_ROLE);
Ok(())
}
#[tokio::test]
async fn rbac_unmapped_procedure_falls_back_to_admin() -> anyhow::Result<()> {
let admin = *ADMIN_ID;
let non_admin = *NON_ADMIN_ID;
let mut builder = MockChain::builder();
let faucet = add_rbac_faucet_with_pause(&mut builder, admin, pause_unpause_roles(), 49, true)?;
let admin_note = build_set_max_supply_note(admin, 500_000)?;
let attacker_note = build_set_max_supply_note(non_admin, 500_000)?;
builder.add_output_note(RawOutputNote::Full(admin_note.clone()));
builder.add_output_note(RawOutputNote::Full(attacker_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &admin_note).await?;
let result = mock_chain
.build_tx_context(faucet.id(), &[attacker_note.id()], &[])?
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_SENDER_LACKS_ROLE);
Ok(())
}
#[test]
fn pausable_storage_default_is_unpaused() {
let storage = PausableStorage::default();
assert!(!storage.state());
assert_eq!(storage.to_word(), Word::default());
}
#[test]
fn pausable_storage_paused_writes_canonical_word() {
let storage = PausableStorage::paused();
assert!(storage.state());
assert_eq!(storage.to_word(), Word::from([1u32, 0, 0, 0]));
}
fn add_faucet_with_pause_and_policies(
builder: &mut MockChainBuilder,
owner: AccountId,
) -> anyhow::Result<Account> {
let faucet = FungibleFaucet::builder()
.name(TokenName::new("SYM")?)
.symbol("SYM".try_into()?)
.decimals(8)
.max_supply(AssetAmount::new(1_000_000)?)
.build()?;
let account_builder = AccountBuilder::new([44u8; 32])
.account_type(AccountType::Public)
.with_asset_callbacks(AssetCallbackFlag::Enabled)
.with_component(faucet)
.with_components(AccessControl::Ownable2Step { owner })
.with_component(Pausable::unpaused())
.with_component(PausableManager)
.with_components(
TokenPolicyManager::builder()
.active_mint_policy(MintPolicy::allow_all())
.active_burn_policy(BurnPolicy::allow_all())
.active_send_policy(TransferPolicy::empty_basic_blocklist())
.active_receive_policy(TransferPolicy::empty_basic_blocklist())
.build(),
);
builder.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)
}
#[tokio::test]
async fn pausable_transfer_succeeds_when_unpaused() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let target = builder.add_existing_wallet(Auth::IncrNonce)?;
let faucet = add_faucet_with_pause_and_policies(&mut builder, *OWNER_ID)?;
let asset = FungibleAsset::new(faucet.id(), 100)?;
let note = builder.add_p2id_note(
faucet.id(),
target.id(),
&[Asset::Fungible(asset)],
NoteType::Public,
)?;
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
let faucet_inputs = mock_chain.get_foreign_account_inputs(faucet.id())?;
mock_chain
.build_tx_context(target.id(), &[note.id()], &[])?
.foreign_accounts(vec![faucet_inputs])
.build()?
.execute()
.await?;
Ok(())
}
#[tokio::test]
async fn pausable_transfer_fails_when_paused() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let target = builder.add_existing_wallet(Auth::IncrNonce)?;
let faucet = add_faucet_with_pause_and_policies(&mut builder, *OWNER_ID)?;
let asset = FungibleAsset::new(faucet.id(), 100)?;
let note = builder.add_p2id_note(
faucet.id(),
target.id(),
&[Asset::Fungible(asset)],
NoteType::Public,
)?;
let pause_note = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
let faucet_inputs = mock_chain.get_foreign_account_inputs(faucet.id())?;
let result = mock_chain
.build_tx_context(target.id(), &[note.id()], &[])?
.foreign_accounts(vec![faucet_inputs])
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_PAUSABLE_IS_PAUSED);
Ok(())
}
#[tokio::test]
async fn pausable_transfer_resumes_after_unpause() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let target = builder.add_existing_wallet(Auth::IncrNonce)?;
let faucet = add_faucet_with_pause_and_policies(&mut builder, *OWNER_ID)?;
let asset = FungibleAsset::new(faucet.id(), 100)?;
let note = builder.add_p2id_note(
faucet.id(),
target.id(),
&[Asset::Fungible(asset)],
NoteType::Public,
)?;
let pause_note = build_pause_note(*OWNER_ID)?;
let unpause_note = build_unpause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
builder.add_output_note(RawOutputNote::Full(unpause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &unpause_note).await?;
let faucet_inputs = mock_chain.get_foreign_account_inputs(faucet.id())?;
mock_chain
.build_tx_context(target.id(), &[note.id()], &[])?
.foreign_accounts(vec![faucet_inputs])
.build()?
.execute()
.await?;
Ok(())
}
#[tokio::test]
async fn pausable_mint_fails_when_paused() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let _target = builder.add_existing_wallet(Auth::IncrNonce)?;
let faucet = add_faucet_with_pause_and_policies(&mut builder, *OWNER_ID)?;
let pause_note = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
let recipient_word = Word::from([0u32, 1, 2, 3]);
let tx_script_code = format!(
r#"
@transaction_script
pub proc main
padw padw push.0
push.{recipient}
push.{note_type}
push.{tag}
push.{amount}
call.::miden::standards::faucets::fungible::mint_and_send
dropw dropw dropw dropw
end
"#,
recipient = recipient_word,
note_type = NoteType::Private as u8,
tag = u32::from(NoteTag::default()),
amount = 100u64,
);
let tx_script =
miden_standards::code_builder::CodeBuilder::default().compile_tx_script(&tx_script_code)?;
let result = mock_chain
.build_tx_context(faucet.id(), &[], &[])?
.tx_script(tx_script)
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_PAUSABLE_IS_PAUSED);
Ok(())
}
#[tokio::test]
async fn pausable_burn_fails_when_paused() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause_and_policies(&mut builder, *OWNER_ID)?;
let burn_asset = FungibleAsset::new(faucet.id(), 50)?;
let burn_note_script_code = r#"
@note_script
pub proc main
dropw
call.::miden::standards::faucets::fungible::receive_and_burn
end
"#;
let burn_note_script = miden_standards::code_builder::CodeBuilder::default()
.compile_note_script(burn_note_script_code)?;
let mut rng = RandomCoin::new(Word::from([1u32, 2, 3, 4]));
let burn_note = NoteBuilder::new(faucet.id(), &mut rng)
.note_type(NoteType::Private)
.add_assets([Asset::Fungible(burn_asset)])
.script(burn_note_script)
.build()?;
builder.add_output_note(RawOutputNote::Full(burn_note.clone()));
let pause_note = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
let result = mock_chain
.build_tx_context(faucet.id(), &[burn_note.id()], &[])?
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_PAUSABLE_IS_PAUSED);
Ok(())
}
fn add_faucet_mutable_max_supply_with_pause(
builder: &mut MockChainBuilder,
owner: AccountId,
) -> anyhow::Result<Account> {
let faucet = FungibleFaucet::builder()
.name(TokenName::new("SYM")?)
.symbol("SYM".try_into()?)
.decimals(8)
.max_supply(AssetAmount::new(1_000_000)?)
.is_max_supply_mutable(true)
.build()?;
let account_builder = AccountBuilder::new([45u8; 32])
.account_type(AccountType::Public)
.with_component(faucet)
.with_components(AccessControl::Ownable2Step { owner })
.with_component(Pausable::unpaused())
.with_component(PausableManager);
builder.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)
}
#[tokio::test]
async fn pausable_set_max_supply_fails_when_paused() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_mutable_max_supply_with_pause(&mut builder, *OWNER_ID)?;
let set_max_supply_note_code = format!(
r#"
@note_script
pub proc main
push.{new_max_supply}
swap drop
call.::miden::standards::faucets::fungible::set_max_supply
end
"#,
new_max_supply = 500_000u64,
);
let set_max_supply_note_script = miden_standards::code_builder::CodeBuilder::default()
.compile_note_script(&set_max_supply_note_code)?;
let mut rng = RandomCoin::new(Word::from([9u32, 8, 7, 6]));
let set_max_supply_note = NoteBuilder::new(*OWNER_ID, &mut rng)
.note_type(NoteType::Private)
.script(set_max_supply_note_script)
.build()?;
builder.add_output_note(RawOutputNote::Full(set_max_supply_note.clone()));
let pause_note = build_pause_note(*OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
let result = mock_chain
.build_tx_context(faucet.id(), &[set_max_supply_note.id()], &[])?
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_PAUSABLE_IS_PAUSED);
Ok(())
}
fn add_faucet_with_pause_auth_controlled(
builder: &mut MockChainBuilder,
) -> anyhow::Result<Account> {
let faucet = FungibleFaucet::builder()
.name(TokenName::new("SYM")?)
.symbol("SYM".try_into()?)
.decimals(8)
.max_supply(AssetAmount::new(1_000_000)?)
.build()?;
let account_builder = AccountBuilder::new([46u8; 32])
.account_type(AccountType::Public)
.with_component(faucet)
.with_component(Authority::AuthControlled)
.with_component(Pausable::unpaused())
.with_component(PausableManager);
builder.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)
}
#[tokio::test]
async fn pausable_manager_works_with_auth_controlled() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
let faucet = add_faucet_with_pause_auth_controlled(&mut builder)?;
let pause_note = build_pause_note(*NON_OWNER_ID)?;
builder.add_output_note(RawOutputNote::Full(pause_note.clone()));
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
Ok(())
}