extern crate alloc;
use alloc::vec::Vec;
use miden_processor::crypto::random::RandomCoin;
use miden_protocol::Felt;
use miden_protocol::account::{Account, AccountId, AccountType};
use miden_protocol::note::Note;
use miden_protocol::testing::account_id::AccountIdBuilder;
use miden_standards::errors::standards::{
ERR_OWNER_CONFIG_TARGET_ACCOUNT_MISMATCH,
ERR_OWNER_CONFIG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS,
ERR_OWNER_CONFIG_UNKNOWN_SELECTOR,
};
use miden_standards::note::{
NetworkAccountTarget,
NoteExecutionHint,
OwnerConfig,
OwnerConfigNote,
};
use miden_standards::testing::note::NoteBuilder;
use miden_testing::{MockChain, assert_transaction_executor_error};
use super::{create_ownable_account, get_nominated_owner_from_storage, get_owner_from_storage};
fn owner_config_note(
sender: AccountId,
account: AccountId,
config: OwnerConfig,
rng: &mut RandomCoin,
) -> anyhow::Result<Note> {
let note = OwnerConfigNote::builder()
.sender(sender)
.target(account)
.config(config)
.generate_serial_number(rng)
.build()?
.into();
Ok(note)
}
fn malformed_owner_config_note(
sender: AccountId,
target: AccountId,
storage: Vec<Felt>,
rng: &mut RandomCoin,
) -> anyhow::Result<Note> {
let note = NoteBuilder::new(sender, rng)
.script(OwnerConfigNote::script())
.note_storage(storage)?
.attachment(NetworkAccountTarget::new(target, NoteExecutionHint::Always)?)
.build()?;
Ok(note)
}
async fn execute_note_and_apply(
mock_chain: &MockChain,
account: &Account,
note: &Note,
) -> anyhow::Result<Account> {
let tx = mock_chain
.build_transaction(account.clone())
.unauthenticated_input_note(note.clone())
.build()?;
let executed = tx.execute().await?;
let mut updated = account.clone();
updated.apply_patch(executed.account_patch())?;
Ok(updated)
}
#[tokio::test]
async fn transfer_then_accept_dispatch() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let new_owner = AccountIdBuilder::new().build_with_seed([2; 32]);
let account = create_ownable_account(owner)?;
let mut builder = MockChain::builder();
builder.add_account(account.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
let transfer = owner_config_note(
owner,
account.id(),
OwnerConfig::TransferOwnership { new_owner: Some(new_owner) },
&mut rng,
)?;
let updated = execute_note_and_apply(&mock_chain, &account, &transfer).await?;
assert_eq!(get_nominated_owner_from_storage(&updated)?, Some(new_owner));
let accept =
owner_config_note(new_owner, updated.id(), OwnerConfig::AcceptOwnership, &mut rng)?;
let final_account = execute_note_and_apply(&mock_chain, &updated, &accept).await?;
assert_eq!(get_owner_from_storage(&final_account)?, Some(new_owner));
assert_eq!(get_nominated_owner_from_storage(&final_account)?, None);
Ok(())
}
#[tokio::test]
async fn renounce_dispatch() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let account = create_ownable_account(owner)?;
let mut builder = MockChain::builder();
builder.add_account(account.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
let note = owner_config_note(owner, account.id(), OwnerConfig::RenounceOwnership, &mut rng)?;
let updated = execute_note_and_apply(&mock_chain, &account, ¬e).await?;
assert_eq!(get_owner_from_storage(&updated)?, None);
Ok(())
}
#[tokio::test]
async fn unknown_selector_fails() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let account = create_ownable_account(owner)?;
let mut builder = MockChain::builder();
builder.add_account(account.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
let note = malformed_owner_config_note(owner, account.id(), vec![Felt::from(99u32)], &mut rng)?;
let tx = mock_chain
.build_transaction(account.clone())
.unauthenticated_input_note(note)
.build()?;
let result = tx.execute().await;
assert_transaction_executor_error!(result, ERR_OWNER_CONFIG_UNKNOWN_SELECTOR);
Ok(())
}
#[tokio::test]
async fn wrong_storage_item_count_fails() -> anyhow::Result<()> {
let owner = AccountIdBuilder::new().build_with_seed([1; 32]);
let account = create_ownable_account(owner)?;
let mut builder = MockChain::builder();
builder.add_account(account.clone())?;
let mock_chain = builder.build()?;
let mut rng = RandomCoin::new([Felt::from(100u32); 4].into());
let note = malformed_owner_config_note(owner, account.id(), vec![Felt::from(0u32)], &mut rng)?;
let tx = mock_chain
.build_transaction(account.clone())
.unauthenticated_input_note(note)
.build()?;
let result = tx.execute().await;
assert_transaction_executor_error!(result, ERR_OWNER_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_ownable_account(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 = owner_config_note(owner, target, OwnerConfig::AcceptOwnership, &mut rng)?;
let result = mock_chain
.build_transaction(decoy.clone())
.unauthenticated_input_note(note)
.build()?
.execute()
.await;
assert_transaction_executor_error!(result, ERR_OWNER_CONFIG_TARGET_ACCOUNT_MISMATCH);
Ok(())
}