use alloc::boxed::Box;
use alloc::string::ToString;
use core::error::Error;
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::note::{Note, NoteScript};
use crate::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet};
use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt};
use crate::account::wallets::BasicWallet;
mod burn;
pub use burn::BurnNote;
mod execution_hint;
pub use execution_hint::NoteExecutionHint;
mod mint;
pub use mint::{MintNote, MintNoteStorage};
mod p2id;
pub use p2id::{P2idNote, P2idNoteStorage};
mod p2ide;
pub use p2ide::{P2ideNote, P2ideNoteStorage};
mod swap;
pub use swap::{SwapNote, SwapNoteStorage};
mod network_account_target;
pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
mod network_note;
pub use network_note::{AccountTargetNetworkNote, NetworkNoteExt};
mod standard_note_attachment;
use miden_protocol::errors::NoteError;
pub use standard_note_attachment::StandardNoteAttachment;
pub enum StandardNote {
P2ID,
P2IDE,
SWAP,
MINT,
BURN,
}
impl StandardNote {
pub fn from_script(script: &NoteScript) -> Option<Self> {
Self::from_script_root(script.root())
}
pub fn from_script_root(root: Word) -> Option<Self> {
if root == P2idNote::script_root() {
return Some(Self::P2ID);
}
if root == P2ideNote::script_root() {
return Some(Self::P2IDE);
}
if root == SwapNote::script_root() {
return Some(Self::SWAP);
}
if root == MintNote::script_root() {
return Some(Self::MINT);
}
if root == BurnNote::script_root() {
return Some(Self::BURN);
}
None
}
pub fn name(&self) -> &'static str {
match self {
Self::P2ID => "P2ID",
Self::P2IDE => "P2IDE",
Self::SWAP => "SWAP",
Self::MINT => "MINT",
Self::BURN => "BURN",
}
}
pub fn expected_num_storage_items(&self) -> usize {
match self {
Self::P2ID => P2idNote::NUM_STORAGE_ITEMS,
Self::P2IDE => P2ideNote::NUM_STORAGE_ITEMS,
Self::SWAP => SwapNote::NUM_STORAGE_ITEMS,
Self::MINT => MintNote::NUM_STORAGE_ITEMS_PRIVATE,
Self::BURN => BurnNote::NUM_STORAGE_ITEMS,
}
}
pub fn script(&self) -> NoteScript {
match self {
Self::P2ID => P2idNote::script(),
Self::P2IDE => P2ideNote::script(),
Self::SWAP => SwapNote::script(),
Self::MINT => MintNote::script(),
Self::BURN => BurnNote::script(),
}
}
pub fn script_root(&self) -> Word {
match self {
Self::P2ID => P2idNote::script_root(),
Self::P2IDE => P2ideNote::script_root(),
Self::SWAP => SwapNote::script_root(),
Self::MINT => MintNote::script_root(),
Self::BURN => BurnNote::script_root(),
}
}
pub fn is_compatible_with(&self, account_interface: &AccountInterface) -> bool {
if account_interface.components().contains(&AccountComponentInterface::BasicWallet) {
return true;
}
let interface_proc_digests = account_interface.get_procedure_digests();
match self {
Self::P2ID | &Self::P2IDE => {
interface_proc_digests.contains(&BasicWallet::receive_asset_digest())
},
Self::SWAP => {
interface_proc_digests.contains(&BasicWallet::receive_asset_digest())
&& interface_proc_digests.contains(&BasicWallet::move_asset_to_note_digest())
},
Self::MINT => {
interface_proc_digests.contains(&NetworkFungibleFaucet::mint_and_send_digest())
},
Self::BURN => {
interface_proc_digests.contains(&BasicFungibleFaucet::burn_digest())
|| interface_proc_digests.contains(&NetworkFungibleFaucet::burn_digest())
},
}
}
pub fn is_consumable(
&self,
note: &Note,
target_account_id: AccountId,
block_ref: BlockNumber,
) -> Option<NoteConsumptionStatus> {
match self.is_consumable_inner(note, target_account_id, block_ref) {
Ok(status) => status,
Err(err) => {
let err: Box<dyn Error + Send + Sync + 'static> = Box::from(err);
Some(NoteConsumptionStatus::NeverConsumable(err))
},
}
}
fn is_consumable_inner(
&self,
note: &Note,
target_account_id: AccountId,
block_ref: BlockNumber,
) -> Result<Option<NoteConsumptionStatus>, NoteError> {
match self {
StandardNote::P2ID => {
let input_account_id = P2idNoteStorage::try_from(note.storage().items())
.map_err(|e| NoteError::other_with_source("invalid P2ID note storage", e))?;
if input_account_id.target() == target_account_id {
Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
} else {
Ok(Some(NoteConsumptionStatus::NeverConsumable("account ID provided to the P2ID note storage doesn't match the target account ID".into())))
}
},
StandardNote::P2IDE => {
let P2ideNoteStorage {
target: receiver_account_id,
reclaim_height,
timelock_height,
} = P2ideNoteStorage::try_from(note.storage().items())
.map_err(|e| NoteError::other_with_source("invalid P2IDE note storage", e))?;
let current_block_height = block_ref.as_u32();
let reclaim_height = reclaim_height.unwrap_or_default().as_u32();
let timelock_height = timelock_height.unwrap_or_default().as_u32();
let consumable_after = reclaim_height.max(timelock_height);
if target_account_id == note.metadata().sender() {
if current_block_height >= consumable_after {
Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
} else {
Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
consumable_after,
))))
}
} else if target_account_id == receiver_account_id {
if current_block_height >= timelock_height {
Ok(Some(NoteConsumptionStatus::ConsumableWithAuthorization))
} else {
Ok(Some(NoteConsumptionStatus::ConsumableAfter(BlockNumber::from(
timelock_height,
))))
}
} else {
Ok(Some(NoteConsumptionStatus::NeverConsumable(
"target account of the transaction does not match neither the receiver account specified by the P2IDE storage, nor the sender account".into()
)))
}
},
_ => Ok(None),
}
}
}
#[derive(Debug)]
pub enum NoteConsumptionStatus {
Consumable,
ConsumableAfter(BlockNumber),
ConsumableWithAuthorization,
UnconsumableConditions,
NeverConsumable(Box<dyn Error + Send + Sync + 'static>),
}
impl Clone for NoteConsumptionStatus {
fn clone(&self) -> Self {
match self {
NoteConsumptionStatus::Consumable => NoteConsumptionStatus::Consumable,
NoteConsumptionStatus::ConsumableAfter(block_height) => {
NoteConsumptionStatus::ConsumableAfter(*block_height)
},
NoteConsumptionStatus::ConsumableWithAuthorization => {
NoteConsumptionStatus::ConsumableWithAuthorization
},
NoteConsumptionStatus::UnconsumableConditions => {
NoteConsumptionStatus::UnconsumableConditions
},
NoteConsumptionStatus::NeverConsumable(error) => {
let err = error.to_string();
NoteConsumptionStatus::NeverConsumable(err.into())
},
}
}
}