miden_standards/note/
burn.rs1use alloc::vec::Vec;
2
3use miden_protocol::Word;
4use miden_protocol::account::AccountId;
5use miden_protocol::assembly::Path;
6use miden_protocol::asset::Asset;
7use miden_protocol::crypto::rand::FeltRng;
8use miden_protocol::errors::NoteError;
9use miden_protocol::note::{
10 Note,
11 NoteAssets,
12 NoteAttachment,
13 NoteAttachments,
14 NoteRecipient,
15 NoteScript,
16 NoteScriptRoot,
17 NoteStorage,
18 NoteType,
19 PartialNoteMetadata,
20};
21use miden_protocol::utils::sync::LazyLock;
22
23use crate::StandardsLib;
24use crate::note::costs::{BURN_CONSUMPTION_CYCLES, NoteConsumptionCost};
25use crate::note::{NetworkAccountTarget, NoteExecutionHint};
26
27const BURN_SCRIPT_PATH: &str = "::miden::standards::notes::burn::main";
32
33static BURN_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
35 let standards_lib = StandardsLib::default();
36 let path = Path::new(BURN_SCRIPT_PATH);
37 NoteScript::from_package_reference(standards_lib.as_ref(), path)
38 .expect("Standards library contains BURN note script procedure")
39});
40
41#[derive(Debug, Clone)]
57pub struct BurnNote {
58 sender: AccountId,
59 serial_number: Word,
60 asset: Asset,
61 attachments: NoteAttachments,
62}
63
64#[bon::bon]
65impl BurnNote {
66 #[builder]
75 pub fn new(
76 #[builder(field)] attachments: Vec<NoteAttachment>,
77 sender: AccountId,
78 #[builder(into)] asset: Asset,
79 serial_number: Word,
80 ) -> Result<Self, NoteError> {
81 let network_target =
82 NetworkAccountTarget::new(asset.faucet_id(), NoteExecutionHint::Always).map_err(
83 |err| {
84 NoteError::other_with_source("failed to target BURN note at asset faucet", err)
85 },
86 )?;
87 let attachments = NoteAttachments::new(
88 core::iter::once(network_target.into()).chain(attachments).collect(),
89 )?;
90
91 Ok(Self {
92 sender,
93 serial_number,
94 asset,
95 attachments,
96 })
97 }
98}
99
100impl BurnNote {
101 pub const NUM_STORAGE_ITEMS: usize = 8;
106
107 pub fn script() -> NoteScript {
112 BURN_SCRIPT.clone()
113 }
114
115 pub fn script_root() -> NoteScriptRoot {
117 BURN_SCRIPT.root()
118 }
119
120 pub fn sender(&self) -> AccountId {
122 self.sender
123 }
124
125 pub fn faucet_id(&self) -> AccountId {
127 self.asset.faucet_id()
128 }
129
130 pub fn serial_number(&self) -> Word {
132 self.serial_number
133 }
134
135 pub fn asset(&self) -> Asset {
137 self.asset
138 }
139
140 pub fn attachments(&self) -> &NoteAttachments {
142 &self.attachments
143 }
144}
145
146impl<S: burn_note_builder::State> BurnNoteBuilder<S> {
150 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
152 self.attachments.push(attachment.into());
153 self
154 }
155
156 pub fn attachments(
158 mut self,
159 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
160 ) -> Self {
161 self.attachments.extend(attachments.into_iter().map(Into::into));
162 self
163 }
164}
165
166impl<S: burn_note_builder::State> BurnNoteBuilder<S>
167where
168 S::SerialNumber: burn_note_builder::IsUnset,
169{
170 pub fn generate_serial_number(
172 self,
173 rng: &mut impl FeltRng,
174 ) -> BurnNoteBuilder<burn_note_builder::SetSerialNumber<S>> {
175 self.serial_number(rng.draw_word())
176 }
177}
178
179impl From<BurnNote> for Note {
183 fn from(note: BurnNote) -> Self {
184 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public);
188 let storage = NoteStorage::new(note.asset.as_elements().to_vec())
189 .expect("an asset always fits in BURN note storage");
190 let recipient = NoteRecipient::new(note.serial_number, BurnNote::script(), storage);
191
192 let assets = NoteAssets::new(vec![note.asset])
193 .expect("a single asset never exceeds the note asset limit");
194 Note::with_attachments(assets, metadata, recipient, note.attachments)
195 }
196}
197
198impl NoteConsumptionCost for BurnNote {
202 fn consumption_cycles() -> u32 {
203 BURN_CONSUMPTION_CYCLES
204 }
205}
206
207#[cfg(test)]
211mod tests {
212 use miden_protocol::account::AccountType;
213 use miden_protocol::asset::FungibleAsset;
214 use miden_protocol::crypto::rand::RandomCoin;
215 use miden_protocol::note::NoteTag;
216
217 use super::*;
218 use crate::note::NetworkAccountTarget;
219
220 fn sender() -> AccountId {
221 AccountId::builder().account_type(AccountType::Private).build_with_seed([1; 32])
222 }
223
224 fn faucet() -> AccountId {
225 AccountId::builder().account_type(AccountType::Public).build_with_seed([2; 32])
226 }
227
228 #[test]
230 fn builder_builds_public_burn_note() {
231 let mut rng = RandomCoin::new(Word::empty());
232 let asset = FungibleAsset::new(faucet(), 100).unwrap();
233
234 let burn_note = BurnNote::builder()
235 .sender(sender())
236 .asset(asset)
237 .generate_serial_number(&mut rng)
238 .build()
239 .unwrap();
240
241 assert_eq!(burn_note.sender(), sender());
242 assert_eq!(burn_note.faucet_id(), faucet());
243 assert_eq!(burn_note.asset(), asset.into());
244 assert_ne!(burn_note.serial_number(), Word::empty());
245
246 let note = Note::from(burn_note);
247 assert_eq!(note.metadata().note_type(), NoteType::Public);
248 assert_eq!(note.metadata().tag(), NoteTag::default());
249 assert_eq!(note.assets().num_assets(), 1);
250 assert_eq!(note.recipient().storage().items(), Asset::from(asset).as_elements());
251
252 let target = NetworkAccountTarget::try_from(note.attachments()).unwrap();
253 assert_eq!(target.target_id(), faucet());
254 assert_eq!(target.execution_hint(), NoteExecutionHint::Always);
255 }
256}