mod support;
use anyhow::{Context, Result};
use miden_processor::crypto::random::RandomCoin;
use miden_protocol::account::{Account, AccountId};
use miden_protocol::errors::MasmError;
use miden_protocol::note::Note;
use miden_protocol::transaction::ExecutedTransaction;
use miden_protocol::{Felt, Word};
use miden_standards::interop::eth::EthEmbeddedAccountId;
use miden_testing::assert_transaction_executor_error;
use miden_tx::TransactionExecutorError;
use miden_usdcx::note::xreserve_admin::XReserveSetAttesterNote;
use miden_usdcx::note::xreserve_mint::DepositAttestation;
use miden_usdcx::vectors::{MiVector, load};
use miden_usdcx::xreserve::encoding::Signature;
use support::*;
fn prod_note_rng(seed: u64) -> RandomCoin {
RandomCoin::new(Word::from([
Felt::from(seed as u32),
Felt::from((seed >> 32) as u32),
Felt::from(3u32),
Felt::from(4u32),
]))
}
fn administrator() -> AccountId {
test_account_id(1)
}
fn dom_pauser() -> AccountId {
test_account_id(2)
}
fn dom_unpauser() -> AccountId {
test_account_id(3)
}
const MAX_SUPPLY: u64 = 1_000_000;
const TOKEN_SUPPLY: u64 = 100_000;
const MIN_BURN_SIZE: u64 = 1_000;
const VALID_BURN: u64 = 5_000;
fn err_paused() -> MasmError {
MasmError::from_static_str("the contract is paused")
}
const BASE_VECTOR: &str = "mi-pos-empty-hookdata";
const MINT_AMOUNT: u64 = 250_000_000;
const MAX_FEE_RAW: u64 = 1;
const REMOTE_RECIPIENT_BYTE_OFF: usize = 19 * 4;
const REMOTE_TOKEN_BYTE_OFF: usize = 11 * 4;
const NONCE_BYTE_OFF: usize = 51 * 4;
fn mi(id: &str) -> &'static MiVector {
load()
.families
.mi
.iter()
.find(|v| v.id == id)
.unwrap_or_else(|| panic!("canonical artifact is missing mp vector {id}"))
}
fn payload_for(
recipient: AccountId,
amount: u64,
nonce_variant: u8,
faucet_id: AccountId,
) -> Vec<u8> {
let mut payload = mi(BASE_VECTOR).payload();
payload[AMOUNT_BYTE_OFF..AMOUNT_BYTE_OFF + 32].copy_from_slice(&uint256_be(amount));
payload[MAX_FEE_BYTE_OFF..MAX_FEE_BYTE_OFF + 32].copy_from_slice(&uint256_be(MAX_FEE_RAW));
payload[REMOTE_RECIPIENT_BYTE_OFF..REMOTE_RECIPIENT_BYTE_OFF + 32]
.copy_from_slice(&EthEmbeddedAccountId::from_account_id(recipient).to_bytes32());
payload[REMOTE_TOKEN_BYTE_OFF..REMOTE_TOKEN_BYTE_OFF + 32]
.copy_from_slice(&EthEmbeddedAccountId::from_account_id(faucet_id).to_bytes32());
payload[NONCE_BYTE_OFF] ^= nonce_variant;
payload
}
fn placeholder_driver_src() -> String {
"#! Test driver stand-in: never invoked by this suite (the custom mint entry was deleted by\n\
#! the Wave-1 S1 recomposition); the guarded fixture only requires a compilable component.\n\
#!\n\
#! Inputs: [pad(16)]\n\
#! Outputs: [pad(16)]\n\
#!\n\
#! Invocation: call\n\
@account_procedure\n\
pub proc drive\n\
\x20\x20\x20\x20push.0 drop\n\
end\n"
.to_string()
}
fn production_pause_fixture() -> Result<GuardedMint> {
let driver = placeholder_driver_src();
let probe = composition_supply_probe_src(0);
setup_guarded_mint_account(
GuardSelection::ProductionAttestation,
MAX_SUPPLY,
0,
Word::from([Felt::from(TEST_DOMAIN), Felt::ZERO, Felt::ZERO, Felt::ZERO]),
None,
None,
&driver,
&probe,
true,
)
}
fn mint_fixture(extra_notes: impl Fn(AccountId) -> Vec<Note>) -> Result<ProductionFaucet> {
setup_production_faucet(0, |recipient, faucet_id| {
let commitment =
gen_attester(1, &payload_for(recipient, MINT_AMOUNT, 0, faucet_id)).commitment;
let route = faucet_id;
let mut notes = vec![
XReserveSetAttesterNote::create(
administrator(),
route,
commitment,
1,
&mut prod_note_rng(952),
)
.expect("building the administrator set_attester note"),
];
notes.extend(extra_notes(faucet_id));
notes
})
}
async fn bring_up(pf: &mut ProductionFaucet, count: usize) -> Result<()> {
for (i, note) in pf.seeded_notes.clone().iter().take(count).enumerate() {
let tx = pf
.mock_chain
.build_transaction(pf.faucet_id)
.authenticated_input_note(note.id())
.build()
.with_context(|| format!("bring-up note {i}: tx build"))?
.execute()
.await
.map_err(|e| anyhow::anyhow!("bring-up note {i} must succeed: {e}"))?;
pf.mock_chain.add_pending_executed_transaction(&tx)?;
pf.mock_chain.prove_next_block()?;
}
Ok(())
}
fn attested_mint_note(pf: &ProductionFaucet, payload: &[u8], rng_seed: u64) -> Result<Note> {
let attester = gen_attester(1, payload);
mint_note_from_payload(
pf.producer_id,
pf.faucet_id,
payload,
DepositAttestation::new(Signature::new(attester.sig_bytes), attester.pubkey.clone()),
&mut prod_note_rng(rng_seed),
)
}
async fn emit_and_consume_mint(
pf: &mut ProductionFaucet,
payload: &[u8],
rng_seed: u64,
) -> Result<std::result::Result<ExecutedTransaction, TransactionExecutorError>> {
let note = attested_mint_note(pf, payload, rng_seed)?;
emit_note_with_attachments(&mut pf.mock_chain, pf.producer_id, ¬e).await?;
Ok(pf
.mock_chain
.build_transaction(pf.faucet_id)
.authenticated_input_note(note.id())
.build()
.context("building the mint consume tx")?
.execute()
.await)
}
fn token_supply_of(account: &Account) -> Result<Felt> {
Ok(read_token_config(account)?[0])
}
#[test]
fn the_xreserve_library_exports_no_pause_procedure() -> Result<()> {
let lib = assemble_xreserve_lib()?;
let exports: Vec<String> = lib
.manifest
.exports()
.filter(|e| e.is_procedure())
.map(|e| e.path().to_string())
.collect();
assert!(
!exports.iter().any(|e| e.contains("pause")),
"pausing is the stock manager's job now; the faucet library must export no pause \
procedure of its own, or two pause surfaces would exist. exports: {exports:?}"
);
Ok(())
}
#[tokio::test]
async fn administrator_has_no_pause_path() -> Result<()> {
let gm = production_pause_fixture()?;
let account = faucet_account(&gm.harness);
let result = run_pause_against(&gm.harness.mock_chain, &account, administrator(), 5).await;
assert_transaction_executor_error!(result, err_sender_lacks_role());
assert_eq!(
read_is_paused(&account)?,
Word::from([0u32, 0, 0, 0]),
"a rejected pause leaves is_paused unpaused"
);
Ok(())
}
#[tokio::test]
async fn administrator_has_no_unpause_path() -> Result<()> {
let gm = production_pause_fixture()?;
let account = faucet_account(&gm.harness);
let paused = run_dom_pauser_pause(&gm.harness.mock_chain, &account, dom_pauser(), 5)
.await
.expect("DOM_PAUSER pauses the faucet");
let mut evolved = account.clone();
evolved.apply_patch(paused.account_patch())?;
assert_eq!(
read_is_paused(&evolved)?,
Word::from([1u32, 0, 0, 0]),
"precondition: the DOM_PAUSER pause really flipped is_paused"
);
let result =
run_stock_unpause_against(&gm.harness.mock_chain, &evolved, administrator(), 6).await;
assert_transaction_executor_error!(result, err_sender_lacks_role());
assert_eq!(
read_is_paused(&evolved)?,
Word::from([1u32, 0, 0, 0]),
"a rejected unpause leaves the faucet paused"
);
Ok(())
}
#[tokio::test]
async fn dom_pauser_pause_halts_mint() -> Result<()> {
let mut pf = mint_fixture(|faucet_id| {
vec![
stock_pause_note(dom_pauser(), faucet_id, 7)
.expect("building the DOM_PAUSER pause note"),
]
})?;
bring_up(&mut pf, 2).await?; assert_eq!(
read_is_paused(&pf.mock_chain.committed_account(pf.faucet_id)?.clone())?,
Word::from([1u32, 0, 0, 0]),
"precondition: the DOM_PAUSER pause really flipped is_paused"
);
let payload = payload_for(pf.recipient_id, MINT_AMOUNT, 1, pf.faucet_id);
let result = emit_and_consume_mint(&mut pf, &payload, 71).await?;
assert_transaction_executor_error!(result, err_paused());
assert_eq!(
committed_token_supply(&pf.mock_chain, pf.faucet_id)?,
miden_protocol::asset::AssetAmount::new(0)?,
"a halted mint must not raise supply"
);
Ok(())
}
#[tokio::test]
async fn dom_pauser_production_pause_note_halts_mint() -> Result<()> {
let mut pf = mint_fixture(|_| vec![])?;
bring_up(&mut pf, 1).await?;
let account = pf.mock_chain.committed_account(pf.faucet_id)?.clone();
let note = stock_pause_note(dom_pauser(), pf.faucet_id, 8)?;
let paused = pf
.mock_chain
.build_transaction(account.clone())
.unauthenticated_input_note(note.clone())
.build()
.expect("production pause tx build")
.execute()
.await
.expect("the DOM_PAUSER production pause note pauses the mint faucet");
let mut evolved = account.clone();
evolved.apply_patch(paused.account_patch())?;
assert_eq!(
read_is_paused(&evolved)?,
Word::from([1u32, 0, 0, 0]),
"precondition: the production pause note really flipped is_paused"
);
let payload = payload_for(pf.recipient_id, MINT_AMOUNT, 2, pf.faucet_id);
let mint_note = attested_mint_note(&pf, &payload, 72)?;
let result = pf
.mock_chain
.build_transaction(evolved)
.unauthenticated_input_note(mint_note.clone())
.build()
.expect("mint consume tx build")
.execute()
.await;
assert_transaction_executor_error!(result, err_paused());
Ok(())
}
#[tokio::test]
async fn dom_pauser_production_pause_note_halts_burn() -> Result<()> {
let bh = setup_burn_policy_account(
BurnGuardSelection::OracleBurnReal,
MAX_SUPPLY,
TOKEN_SUPPLY,
MIN_BURN_SIZE,
VALID_BURN,
)?;
let BurnPolicyHarness {
mut chain,
faucet_id,
user_id,
burn_note,
asset,
..
} = bh;
let tx0 = try_emit_burn_note(&chain, &burn_note, &asset, faucet_id, user_id)
.await
.expect("the user emits the burn note (test-setup invariant)");
chain.add_pending_executed_transaction(&tx0)?;
chain.prove_next_block()?;
let account = chain.committed_account(faucet_id)?.clone();
let note = stock_pause_note(dom_pauser(), faucet_id, 8)?;
let paused = chain
.build_transaction(account.clone())
.unauthenticated_input_note(note.clone())
.build()
.expect("production pause tx build")
.execute()
.await
.expect("the DOM_PAUSER production pause note pauses the burn faucet");
let mut evolved = account.clone();
evolved.apply_patch(paused.account_patch())?;
let result = chain
.build_transaction(evolved)
.authenticated_input_note(burn_note.id())
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, err_paused());
Ok(())
}
#[tokio::test]
async fn dom_pauser_pause_halts_burn() -> Result<()> {
let bh = setup_burn_policy_account(
BurnGuardSelection::OracleBurnReal,
MAX_SUPPLY,
TOKEN_SUPPLY,
MIN_BURN_SIZE,
VALID_BURN,
)?;
let BurnPolicyHarness {
mut chain,
faucet_id,
user_id,
burn_note,
asset,
..
} = bh;
let tx0 = try_emit_burn_note(&chain, &burn_note, &asset, faucet_id, user_id)
.await
.expect("the user emits the burn note (test-setup invariant)");
chain.add_pending_executed_transaction(&tx0)?;
chain.prove_next_block()?;
let account = chain.committed_account(faucet_id)?.clone();
let paused = run_dom_pauser_pause(&chain, &account, dom_pauser(), 5)
.await
.expect("DOM_PAUSER pauses the burn faucet");
let mut evolved = account.clone();
evolved.apply_patch(paused.account_patch())?;
let result = chain
.build_transaction(evolved)
.authenticated_input_note(burn_note.id())
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, err_paused());
Ok(())
}
#[tokio::test]
async fn dom_pauser_unpause_resumes_mint_and_burn() -> Result<()> {
let mut pf = mint_fixture(|faucet_id| {
vec![
stock_pause_note(dom_pauser(), faucet_id, 9)
.expect("building the DOM_PAUSER pause note"),
stock_unpause_note(dom_unpauser(), faucet_id, 10)
.expect("building the DOM_UNPAUSER unpause note"),
]
})?;
bring_up(&mut pf, 3).await?; assert_eq!(
read_is_paused(&pf.mock_chain.committed_account(pf.faucet_id)?.clone())?,
Word::from([0u32, 0, 0, 0]),
"precondition: the pause→unpause round trip leaves the faucet unpaused"
);
let payload = payload_for(pf.recipient_id, MINT_AMOUNT, 3, pf.faucet_id);
let minted = emit_and_consume_mint(&mut pf, &payload, 73)
.await?
.map_err(|e| anyhow::anyhow!("after unpause, the real attested mint mints again: {e}"))?;
assert_eq!(
minted.output_notes().num_notes(),
1,
"unpause resumes minting (one recipient note)"
);
pf.mock_chain.add_pending_executed_transaction(&minted)?;
pf.mock_chain.prove_next_block()?;
assert_eq!(
committed_token_supply(&pf.mock_chain, pf.faucet_id)?,
miden_protocol::asset::AssetAmount::new(MINT_AMOUNT)?,
"token_supply rose by exactly the attested amount"
);
let bh = setup_burn_policy_account(
BurnGuardSelection::OracleBurnReal,
MAX_SUPPLY,
TOKEN_SUPPLY,
MIN_BURN_SIZE,
VALID_BURN,
)?;
let BurnPolicyHarness {
mut chain,
faucet_id,
user_id,
burn_note,
asset,
..
} = bh;
let tx0 = try_emit_burn_note(&chain, &burn_note, &asset, faucet_id, user_id)
.await
.expect("the user emits the burn note (test-setup invariant)");
chain.add_pending_executed_transaction(&tx0)?;
chain.prove_next_block()?;
let bacct = chain.committed_account(faucet_id)?.clone();
let bpaused = run_dom_pauser_pause(&chain, &bacct, dom_pauser(), 7)
.await
.expect("DOM_PAUSER pauses the burn faucet");
let mut bevolved = bacct.clone();
bevolved.apply_patch(bpaused.account_patch())?;
let bunpaused = run_dom_pauser_unpause(&chain, &bevolved, dom_unpauser(), 8)
.await
.expect("DOM_UNPAUSER unpauses the burn faucet");
bevolved.apply_patch(bunpaused.account_patch())?;
let burned = chain
.build_transaction(bevolved.clone())
.authenticated_input_note(burn_note.id())
.build()?
.execute()
.await
.expect("after unpause, the real receive_and_burn decrements supply");
let mut bfinal = bevolved.clone();
bfinal.apply_patch(burned.account_patch())?;
assert_eq!(
token_supply_of(&bfinal)?,
Felt::from((TOKEN_SUPPLY - VALID_BURN) as u32),
"unpause resumes burning (token_supply decremented by the burn amount)"
);
Ok(())
}