use std::collections::BTreeSet;
use anyhow::{Context, Result};
use miden_processor::crypto::random::RandomCoin;
use miden_protocol::account::component::AccountComponentMetadata;
use miden_protocol::account::{Account, AccountComponent, AccountId, RoleSymbol, StorageMapKey};
use miden_protocol::asset::AssetAmount;
use miden_protocol::errors::MasmError;
use miden_protocol::note::{Note, NoteType};
use miden_protocol::transaction::{ExecutedTransaction, RawOutputNote};
use miden_protocol::{Felt, Word};
use miden_standards::account::access::{
Authority,
Pausable,
PausableManager,
PausableStorage,
RoleBasedAccessControl,
RoleConfig,
};
use miden_standards::account::policies::{BasicBlocklist, BlocklistManager, BlocklistStorage};
use miden_standards::code_builder::CodeBuilder;
use miden_standards::note::config::{
BlocklistConfig,
BlocklistConfigNote,
FaucetMetadataConfig,
FaucetMetadataConfigNote,
PauseConfig,
PauseConfigNote,
};
use miden_standards::testing::note::NoteBuilder;
use miden_testing::{Auth, MockChain, MockChainBuilder};
use miden_tx::TransactionExecutorError;
use miden_usdcx::account::xreserve::{BLK_MANAGER_ROLE, DOM_PAUSER_ROLE, XReserveAdminAuthority};
use miden_usdcx::note::xreserve_admin::XReserveMinBurnAmountNote;
use super::{ProductionFaucet, add_faucet_account, setup_production_faucet, test_account_id};
pub const PRODUCTION_ALLOWLIST_ROOTS: usize = 12;
pub const DOM_PAUSER_ROLE_FELT: u64 = 728_098_706_988_649;
pub const BLK_MANAGER_ROLE_FELT: u64 = 7_907_587_873_290_749;
pub fn admin_holder() -> AccountId {
test_account_id(1)
}
pub fn pauser_holder() -> AccountId {
test_account_id(2)
}
pub fn unpauser_holder() -> AccountId {
test_account_id(3)
}
pub fn blocklist_holder() -> AccountId {
test_account_id(4)
}
pub fn stranger() -> AccountId {
test_account_id(99)
}
pub fn pauser_symbol() -> RoleSymbol {
RoleSymbol::new(DOM_PAUSER_ROLE).expect("the Domain pauser role symbol is valid")
}
pub fn blocklist_symbol() -> RoleSymbol {
RoleSymbol::new(BLK_MANAGER_ROLE).expect("the blocklist administrator role symbol is valid")
}
pub fn err_paused() -> MasmError {
MasmError::from_static_str("the contract is paused")
}
pub fn err_sender_not_role_admin() -> MasmError {
MasmError::from_static_str("note sender does not hold the role's admin role")
}
pub fn set_word() -> Word {
Word::from([Felt::from(1u32), Felt::ZERO, Felt::ZERO, Felt::ZERO])
}
pub fn grounding_components() -> Vec<AccountComponent> {
let rbac = RoleBasedAccessControl::builder()
.role(RoleConfig::new(RoleBasedAccessControl::admin_role()).with_members([admin_holder()]))
.role(RoleConfig::new(pauser_symbol()).with_members([pauser_holder()]))
.role(RoleConfig::new(blocklist_symbol()).with_members([blocklist_holder()]))
.build()
.expect("the grounding RBAC seed must build");
vec![
Pausable::unpaused().into(),
PausableManager.into(),
BasicBlocklist::default().into(),
BlocklistManager.into(),
rbac.into(),
XReserveAdminAuthority::new().into(),
]
}
pub fn setup_grounding(
seed_notes_for: impl FnOnce(AccountId) -> Vec<Note>,
) -> Result<(MockChain, Account, Vec<Note>)> {
let mut mc = MockChainBuilder::new();
let account = add_faucet_account(&mut mc, Auth::IncrNonce, grounding_components())
.context("adding the grounding account")?;
let notes = seed_notes_for(account.id());
for note in ¬es {
mc.add_output_note(RawOutputNote::Full(note.clone()));
}
let chain = mc.build().context("building the grounding chain")?;
Ok((chain, account, notes))
}
pub async fn consume_against(
chain: &MockChain,
account: &Account,
note: &Note,
) -> std::result::Result<ExecutedTransaction, TransactionExecutorError> {
chain
.build_transaction(account.id())
.authenticated_input_note(note.id())
.build()
.expect("building the consume transaction")
.execute()
.await
}
pub async fn consume_and_commit_against(
chain: &mut MockChain,
account: &Account,
note: &Note,
what: &str,
) -> Result<Account> {
let tx = consume_against(chain, account, note)
.await
.map_err(|e| anyhow::anyhow!("{what}: {e}"))?;
chain
.add_pending_executed_transaction(&tx)
.with_context(|| format!("queueing the transaction of {what}"))?;
chain
.prove_next_block()
.with_context(|| format!("committing the block of {what}"))?;
Ok(chain
.committed_account(account.id())
.with_context(|| format!("reading the committed account after {what}"))?
.clone())
}
pub fn admin_faucet(
seed_notes_for: impl FnOnce(AccountId) -> Vec<Note>,
) -> Result<ProductionFaucet> {
setup_production_faucet(0, |_recipient, faucet_id| seed_notes_for(faucet_id))
}
pub async fn consume(
pf: &ProductionFaucet,
note: &Note,
) -> std::result::Result<ExecutedTransaction, TransactionExecutorError> {
pf.mock_chain
.build_transaction(pf.faucet_id)
.authenticated_input_note(note.id())
.build()
.expect("building the consume transaction")
.execute()
.await
}
pub async fn consume_and_commit(
pf: &mut ProductionFaucet,
note: &Note,
what: &str,
) -> Result<Account> {
let tx = consume(pf, note).await.map_err(|e| anyhow::anyhow!("{what}: {e}"))?;
pf.mock_chain.add_pending_executed_transaction(&tx)?;
pf.mock_chain.prove_next_block()?;
Ok(pf
.mock_chain
.committed_account(pf.faucet_id)
.with_context(|| format!("reading the committed faucet after {what}"))?
.clone())
}
pub fn serial(seed: u32) -> Word {
Word::from([seed, 31, 37, 41])
}
pub fn note_rng(seed: u64) -> RandomCoin {
RandomCoin::new(Word::from([
Felt::from(seed as u32),
Felt::from((seed >> 32) as u32),
Felt::from(23u32),
Felt::from(29u32),
]))
}
pub fn pause_action_note(
sender: AccountId,
account: AccountId,
action: PauseConfig,
seed: u32,
) -> Result<Note> {
let note = PauseConfigNote::builder()
.sender(sender)
.target(account)
.config(action)
.serial_number(serial(seed))
.build()
.map_err(|e| anyhow::anyhow!("building the pause action note: {e}"))?;
Ok(Note::from(note))
}
pub fn freeze_note(sender: AccountId, seed: u64) -> Result<Note> {
let src = "use miden::standards::components::access::authority\n\
@note_script\n\
pub proc main\n\
\x20\x20\x20\x20repeat.16 push.0 end\n\
\x20\x20\x20\x20call.authority::freeze\n\
\x20\x20\x20\x20dropw dropw dropw dropw\n\
end\n";
let script = CodeBuilder::new()
.with_dynamically_linked_package(Authority::code().clone())
.context("linking the authority component into the freeze note script")?
.compile_note_script(src)
.map_err(|e| anyhow::anyhow!("the freeze note script failed to compile: {e}"))?;
let mut rng = RandomCoin::new(Word::from([
Felt::from(seed as u32),
Felt::from((seed >> 32) as u32),
Felt::from(17u32),
Felt::from(19u32),
]));
NoteBuilder::new(sender, &mut rng)
.note_type(NoteType::Public)
.script(script)
.build()
.map_err(|e| anyhow::anyhow!("building the freeze note: {e}"))
}
pub fn xreserve_root_probe_component() -> Result<AccountComponent> {
let library = super::assemble_xreserve_lib()?;
AccountComponent::new(
library,
Vec::new(),
AccountComponentMetadata::new("xusdc-xreserve-root-probe"),
)
.context("binding the assembled xreserve library to read a procedure root")
}
pub fn read_paused(account: &Account) -> Result<Word> {
account
.storage()
.get_item(PausableStorage::is_paused_slot())
.map_err(|e| anyhow::anyhow!("reading the is_paused slot: {e}"))
}
pub fn read_blocked(account: &Account, target: AccountId) -> Result<Word> {
let key = Word::from([Felt::ZERO, Felt::ZERO, target.suffix(), target.prefix().as_felt()]);
account
.storage()
.get_map_item(BlocklistStorage::blocked_accounts_slot(), StorageMapKey::new(key))
.map_err(|e| anyhow::anyhow!("reading blocked_accounts[{target}]: {e}"))
}
pub fn callable_roots(account: &Account) -> BTreeSet<Word> {
account.code().procedures().iter().map(|root| Word::from(*root)).collect()
}
fn config_note_serial(seed: u64) -> Word {
Word::from([
Felt::from(seed as u32),
Felt::from((seed >> 32) as u32),
Felt::from(43u32),
Felt::from(47u32),
])
}
pub fn stock_pause_action_note(
sender: AccountId,
faucet_id: AccountId,
action: PauseConfig,
seed: u64,
) -> Result<Note> {
let note = PauseConfigNote::builder()
.sender(sender)
.target(faucet_id)
.config(action)
.serial_number(config_note_serial(seed))
.build()
.map_err(|e| anyhow::anyhow!("building the stock pause action note: {e}"))?;
Ok(Note::from(note))
}
pub fn stock_pause_note(sender: AccountId, faucet_id: AccountId, seed: u64) -> Result<Note> {
stock_pause_action_note(sender, faucet_id, PauseConfig::Pause, seed)
}
pub fn stock_unpause_note(sender: AccountId, faucet_id: AccountId, seed: u64) -> Result<Note> {
stock_pause_action_note(sender, faucet_id, PauseConfig::Unpause, seed)
}
pub fn stock_block_note(
sender: AccountId,
faucet_id: AccountId,
target: AccountId,
seed: u64,
) -> Result<Note> {
stock_block_action_note(
sender,
faucet_id,
BlocklistConfig::BlockAccount { account: target },
seed,
)
}
pub fn stock_unblock_note(
sender: AccountId,
faucet_id: AccountId,
target: AccountId,
seed: u64,
) -> Result<Note> {
stock_block_action_note(
sender,
faucet_id,
BlocklistConfig::UnblockAccount { account: target },
seed,
)
}
pub fn stock_block_action_note(
sender: AccountId,
faucet_id: AccountId,
config: BlocklistConfig,
seed: u64,
) -> Result<Note> {
let note = BlocklistConfigNote::builder()
.sender(sender)
.target(faucet_id)
.config(config)
.serial_number(config_note_serial(seed))
.build()
.map_err(|e| anyhow::anyhow!("building the stock blocklist config note: {e}"))?;
Ok(Note::from(note))
}
pub fn stock_min_burn_note(
sender: AccountId,
faucet_id: AccountId,
new_min: u64,
seed: u64,
) -> Result<Note> {
let mut rng = RandomCoin::new(config_note_serial(seed));
let new_min = AssetAmount::new(new_min)
.map_err(|e| anyhow::anyhow!("min-burn amount out of range: {e}"))?;
XReserveMinBurnAmountNote::builder()
.sender(sender)
.target(faucet_id)
.min_burn_amount(new_min)
.generate_serial_number(&mut rng)
.build()
.map_err(|e| anyhow::anyhow!("building the min-burn note: {e}"))
}
pub fn stock_set_max_supply_note(
sender: AccountId,
faucet_id: AccountId,
new_max_supply: u64,
seed: u64,
) -> Result<Note> {
let max_supply = AssetAmount::new(new_max_supply)
.map_err(|e| anyhow::anyhow!("max supply out of range: {e}"))?;
let note = FaucetMetadataConfigNote::builder()
.sender(sender)
.target(faucet_id)
.config(FaucetMetadataConfig::SetMaxSupply { max_supply })
.serial_number(config_note_serial(seed))
.build()
.map_err(|e| anyhow::anyhow!("building the stock set-max-supply note: {e}"))?;
Ok(Note::from(note))
}