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::NetworkAccountTarget;
25use crate::note::costs::{BURN_CONSUMPTION_CYCLES, NoteConsumptionCost};
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)]
62pub struct BurnNote {
63 sender: AccountId,
64 serial_number: Word,
65 asset: Asset,
66 attachments: NoteAttachments,
67}
68
69#[bon::bon]
70impl BurnNote {
71 #[builder]
81 pub fn new(
82 #[builder(field)] mut attachments: Vec<NoteAttachment>,
83 sender: AccountId,
84 #[builder(into)] asset: Asset,
85 serial_number: Word,
86 ) -> Result<Self, NoteError> {
87 NetworkAccountTarget::ensure_presence_if_public(&mut attachments, asset.faucet_id())
90 .map_err(|err| {
91 NoteError::other_with_source("failed to target the BURN note at its faucet", err)
92 })?;
93
94 let attachments = NoteAttachments::new(attachments)?;
95
96 Ok(Self {
97 sender,
98 serial_number,
99 asset,
100 attachments,
101 })
102 }
103}
104
105impl BurnNote {
106 pub const NUM_STORAGE_ITEMS: usize = 8;
111
112 pub fn script() -> NoteScript {
117 BURN_SCRIPT.clone()
118 }
119
120 pub fn script_root() -> NoteScriptRoot {
122 BURN_SCRIPT.root()
123 }
124
125 pub fn sender(&self) -> AccountId {
127 self.sender
128 }
129
130 pub fn faucet_id(&self) -> AccountId {
132 self.asset.faucet_id()
133 }
134
135 pub fn serial_number(&self) -> Word {
137 self.serial_number
138 }
139
140 pub fn asset(&self) -> Asset {
142 self.asset
143 }
144
145 pub fn attachments(&self) -> &NoteAttachments {
147 &self.attachments
148 }
149}
150
151impl<S: burn_note_builder::State> BurnNoteBuilder<S> {
155 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
157 self.attachments.push(attachment.into());
158 self
159 }
160
161 pub fn attachments(
163 mut self,
164 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
165 ) -> Self {
166 self.attachments.extend(attachments.into_iter().map(Into::into));
167 self
168 }
169}
170
171impl<S: burn_note_builder::State> BurnNoteBuilder<S>
172where
173 S::SerialNumber: burn_note_builder::IsUnset,
174{
175 pub fn generate_serial_number(
177 self,
178 rng: &mut impl FeltRng,
179 ) -> BurnNoteBuilder<burn_note_builder::SetSerialNumber<S>> {
180 self.serial_number(rng.draw_word())
181 }
182}
183
184impl From<BurnNote> for Note {
188 fn from(note: BurnNote) -> Self {
189 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public);
193 let storage = NoteStorage::new(note.asset.as_elements().to_vec())
194 .expect("an asset always fits in BURN note storage");
195 let recipient = NoteRecipient::new(note.serial_number, BurnNote::script(), storage);
196
197 let assets = NoteAssets::new(vec![note.asset])
198 .expect("a single asset never exceeds the note asset limit");
199 Note::with_attachments(assets, metadata, recipient, note.attachments)
200 }
201}
202
203impl NoteConsumptionCost for BurnNote {
207 fn consumption_cycles() -> u32 {
208 BURN_CONSUMPTION_CYCLES
209 }
210}
211
212#[cfg(test)]
216mod tests {
217 use miden_protocol::account::AccountType;
218 use miden_protocol::asset::FungibleAsset;
219 use miden_protocol::crypto::rand::RandomCoin;
220 use miden_protocol::note::NoteTag;
221
222 use super::*;
223 use crate::note::{NetworkNoteExt, NoteExecutionHint};
224
225 fn sender() -> AccountId {
226 AccountId::builder().account_type(AccountType::Private).build_with_seed([1; 32])
227 }
228
229 fn faucet() -> AccountId {
230 AccountId::builder().account_type(AccountType::Public).build_with_seed([2; 32])
231 }
232
233 fn private_faucet() -> AccountId {
234 AccountId::builder().account_type(AccountType::Private).build_with_seed([2; 32])
235 }
236
237 fn build_burn_note(faucet_id: AccountId) -> BurnNote {
238 let mut rng = RandomCoin::new(Word::empty());
239 BurnNote::builder()
240 .sender(sender())
241 .asset(FungibleAsset::new(faucet_id, 100).unwrap())
242 .generate_serial_number(&mut rng)
243 .build()
244 .unwrap()
245 }
246
247 #[test]
251 fn builder_builds_public_burn_note() {
252 let asset = FungibleAsset::new(faucet(), 100).unwrap();
253
254 let burn_note = build_burn_note(faucet());
255
256 assert_eq!(burn_note.sender(), sender());
257 assert_eq!(burn_note.faucet_id(), faucet());
258 assert_eq!(burn_note.asset(), asset.into());
259 assert_ne!(burn_note.serial_number(), Word::empty());
260 assert_eq!(burn_note.attachments().num_attachments(), 1);
261
262 let note = Note::from(burn_note);
263 assert_eq!(note.metadata().note_type(), NoteType::Public);
264 assert_eq!(note.metadata().tag(), NoteTag::default());
265 assert_eq!(note.assets().num_assets(), 1);
266 assert_eq!(note.recipient().storage().items(), Asset::from(asset).as_elements());
267 assert!(note.is_network_note());
268
269 let target = NetworkAccountTarget::try_from(note.attachments()).unwrap();
270 assert_eq!(target.target_id(), faucet());
271 assert_eq!(target.execution_hint(), NoteExecutionHint::Always);
272 }
273
274 #[test]
277 fn builder_omits_network_target_for_private_faucet() {
278 let burn_note = build_burn_note(private_faucet());
279
280 assert_eq!(burn_note.attachments().num_attachments(), 0);
281 assert!(!Note::from(burn_note).is_network_note());
282 }
283}