use miden_protocol::account::AccountId;
use miden_protocol::asset::{Asset, AssetAmount, FungibleAsset};
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachmentScheme,
NoteAttachments,
NoteRecipient,
NoteScript,
NoteScriptRoot,
NoteStorage,
NoteTag,
NoteType,
PartialNoteMetadata,
};
use miden_standards::note::{BurnNote, StandardNoteAttachment};
use crate::xreserve::encoding::{EncodingError, XReserveBurnItems};
pub const FIXED_XUSDC_BURN_TAG: u32 = 0x4255_524e;
pub const XRESERVE_BURN_WITHDRAWAL_ATTACHMENT_SCHEME: u16 =
StandardNoteAttachment::UsdcxBurn.attachment_scheme().as_u16();
pub struct XUsdcBurnAttachment {
items: XReserveBurnItems,
}
impl XUsdcBurnAttachment {
pub const NUM_WORDS: usize = 3;
pub fn new(items: XReserveBurnItems) -> Self {
Self { items }
}
pub fn items(&self) -> &XReserveBurnItems {
&self.items
}
pub fn into_items(self) -> XReserveBurnItems {
self.items
}
}
impl From<&XUsdcBurnAttachment> for NoteAttachment {
fn from(attachment: &XUsdcBurnAttachment) -> Self {
NoteAttachment::with_words(
NoteAttachmentScheme::new(XRESERVE_BURN_WITHDRAWAL_ATTACHMENT_SCHEME)
.expect("the withdrawal scheme is neither reserved nor past the protocol maximum"),
attachment.items.encode().to_vec(),
)
.expect("the withdrawal payload is within the per-attachment word cap")
}
}
impl TryFrom<&NoteAttachment> for XUsdcBurnAttachment {
type Error = EncodingError;
fn try_from(attachment: &NoteAttachment) -> Result<Self, Self::Error> {
if attachment.attachment_scheme().as_u16() != XRESERVE_BURN_WITHDRAWAL_ATTACHMENT_SCHEME {
return Err(EncodingError::BurnItemsMalformed);
}
let items = XReserveBurnItems::decode(attachment.content().as_words())?;
Ok(Self { items })
}
}
const _: () = assert!(
XUsdcBurnAttachment::NUM_WORDS <= NoteAttachment::MAX_NUM_WORDS as usize,
"the withdrawal payload must fit in one attachment"
);
pub struct XReserveBurnNote;
impl XReserveBurnNote {
pub fn script() -> NoteScript {
BurnNote::script()
}
pub fn script_root() -> NoteScriptRoot {
BurnNote::script_root()
}
pub fn create<R: FeltRng>(
sender: AccountId,
faucet_id: AccountId,
amount: AssetAmount,
items: XReserveBurnItems,
rng: &mut R,
) -> Result<Note, NoteError> {
Self::builder()
.sender(sender)
.faucet_id(faucet_id)
.amount(amount)
.items(items)
.rng(rng)
.build()
}
}
#[bon::bon]
impl XReserveBurnNote {
#[builder]
pub fn new<R: FeltRng>(
sender: AccountId,
faucet_id: AccountId,
amount: AssetAmount,
items: XReserveBurnItems,
rng: &mut R,
) -> Result<Note, NoteError> {
let serial_num = rng.draw_word();
let asset = FungibleAsset::new(faucet_id, u64::from(amount))
.map_err(|err| NoteError::other_with_source("invalid burned xUSDC asset", err))?;
let storage = NoteStorage::new(Asset::from(asset).as_elements().to_vec())?;
let recipient = NoteRecipient::new(serial_num, BurnNote::script(), storage);
let metadata = PartialNoteMetadata::new(sender, NoteType::Public)
.with_tag(NoteTag::new(FIXED_XUSDC_BURN_TAG));
let vault = NoteAssets::new(vec![asset.into()])?;
let attachments = NoteAttachments::new(vec![
super::network_routing_attachment(faucet_id)?,
NoteAttachment::from(&XUsdcBurnAttachment::new(items)),
])?;
Ok(Note::with_attachments(vault, metadata, recipient, attachments))
}
}