extern crate alloc;
use alloc::vec::Vec;
use miden_processor::crypto::random::RandomCoin;
use miden_protocol::account::{Account, AccountBuilder, AccountId, AccountType, AssetCallbackFlag};
use miden_protocol::asset::AssetAmount;
use miden_protocol::note::Note;
use miden_protocol::testing::account_id::AccountIdBuilder;
use miden_protocol::{Felt, Word};
use miden_standards::account::access::AccessControl;
use miden_standards::account::faucets::{FungibleFaucet, TokenName};
use miden_standards::account::policies::{
BurnPolicy,
MinBurnAmount,
MintPolicy,
TokenPolicyManager,
TransferPolicy,
};
use miden_standards::errors::standards::{
ERR_MIN_BURN_AMOUNT_CONFIG_TARGET_ACCOUNT_MISMATCH,
ERR_MIN_BURN_AMOUNT_CONFIG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS,
};
use miden_standards::note::{MinBurnAmountConfigNote, NetworkAccountTarget, NoteExecutionHint};
use miden_standards::testing::note::NoteBuilder;
use miden_testing::{Auth, MockChain, assert_transaction_executor_error};
const INITIAL_MIN_BURN_AMOUNT: u64 = 50;
fn create_faucet_with_min_burn_amount(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 token_policy_manager = TokenPolicyManager::builder()
.active_mint_policy(MintPolicy::allow_all())
.active_burn_policy(BurnPolicy::min_burn_amount(AssetAmount::new(INITIAL_MIN_BURN_AMOUNT)?))
.active_send_policy(TransferPolicy::allow_all())
.active_receive_policy(TransferPolicy::allow_all())
.build();
let account = AccountBuilder::new([43; 32])
.account_type(AccountType::Public)
.with_components(Auth::IncrNonce)
.with_components(AccessControl::Ownable2Step { owner })
.with_component(faucet)
.with_asset_callbacks(AssetCallbackFlag::from(token_policy_manager.has_transfer_policy()))
.with_components(token_policy_manager)
.build_existing()?;
Ok(account)
}
fn min_burn_amount(account: &Account) -> anyhow::Result<Word> {
Ok(account.storage().get_item(MinBurnAmount::slot_name())?)
}
fn min_burn_amount_config_note(
sender: AccountId,
account: AccountId,
amount: u64,
rng: &mut RandomCoin,
) -> anyhow::Result<Note> {
let note = MinBurnAmountConfigNote::builder()
.sender(sender)
.target(account)
.min_burn_amount(AssetAmount::new(amount)?)
.generate_serial_number(rng)
.build()?
.into();
Ok(note)
}
fn malformed_min_burn_amount_config_note(
sender: AccountId,
target: AccountId,
storage: Vec<Felt>,
rng: &mut RandomCoin,
) -> anyhow::Result<Note> {
let note = NoteBuilder::new(sender, rng)
.script(MinBurnAmountConfigNote::script())
.note_storage(storage)?
.attachment(NetworkAccountTarget::new(target, NoteExecutionHint::Always)?)
.build()?;
Ok(note)
}
#[tokio::test]
async fn owner_sets_min_burn_amount() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let faucet = create_faucet_with_min_burn_amount(owner)?;
let mut builder = MockChain::builder();
builder.add_account(faucet.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
assert_eq!(min_burn_amount(&faucet)?, Word::from([INITIAL_MIN_BURN_AMOUNT as u32, 0, 0, 0]));
let note = min_burn_amount_config_note(owner, faucet.id(), 5, &mut rng)?;
let executed = mock_chain
.build_transaction(faucet.clone())
.unauthenticated_input_note(note)
.build()?
.execute()
.await?;
let mut updated = faucet.clone();
updated.apply_patch(executed.account_patch())?;
assert_eq!(min_burn_amount(&updated)?, Word::from([5u32, 0, 0, 0]));
Ok(())
}
#[tokio::test]
async fn wrong_storage_item_count_fails() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let faucet = create_faucet_with_min_burn_amount(owner)?;
let mut builder = MockChain::builder();
builder.add_account(faucet.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
let note = malformed_min_burn_amount_config_note(
owner,
faucet.id(),
vec![Felt::from(5u32), Felt::from(0u32)],
&mut rng,
)?;
let result = mock_chain
.build_transaction(faucet.clone())
.unauthenticated_input_note(note)
.build()?
.execute()
.await;
assert_transaction_executor_error!(
result,
ERR_MIN_BURN_AMOUNT_CONFIG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS
);
Ok(())
}
#[tokio::test]
async fn decoy_account_cannot_consume_note_of_another_account() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let decoy = create_faucet_with_min_burn_amount(owner)?;
let mut builder = MockChain::builder();
builder.add_account(decoy.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
let target = AccountId::builder().account_type(AccountType::Public).build_with_seed([9; 32]);
let note = min_burn_amount_config_note(owner, target, 5, &mut rng)?;
let result = mock_chain
.build_transaction(decoy.clone())
.unauthenticated_input_note(note)
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_MIN_BURN_AMOUNT_CONFIG_TARGET_ACCOUNT_MISMATCH);
Ok(())
}