1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::assembly::Path;
5use miden_protocol::asset::Asset;
6use miden_protocol::crypto::rand::FeltRng;
7use miden_protocol::errors::NoteError;
8use miden_protocol::note::{
9 Note,
10 NoteAssets,
11 NoteAttachment,
12 NoteAttachments,
13 NoteRecipient,
14 NoteScript,
15 NoteScriptRoot,
16 NoteStorage,
17 NoteTag,
18 NoteType,
19 PartialNoteMetadata,
20};
21use miden_protocol::utils::sync::LazyLock;
22use miden_protocol::{Felt, MAX_NOTE_STORAGE_ITEMS, Word};
23
24use crate::StandardsLib;
25use crate::note::costs::{MINT_CONSUMPTION_CYCLES, NoteConsumptionCost};
26use crate::note::{NetworkAccountTarget, NumStorageItems, P2idNote};
27
28const MINT_SCRIPT_PATH: &str = "::miden::standards::notes::mint::main";
33
34static MINT_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
36 let standards_lib = StandardsLib::default();
37 let path = Path::new(MINT_SCRIPT_PATH);
38 NoteScript::from_package_reference(standards_lib.as_ref(), path)
39 .expect("Standards library contains MINT note script procedure")
40});
41
42#[derive(Debug, Clone)]
63pub struct MintNote {
64 sender: AccountId,
65 storage: MintNoteStorage,
66 serial_number: Word,
67 attachments: NoteAttachments,
68}
69
70#[bon::bon]
71impl MintNote {
72 #[builder]
82 pub fn new(
83 #[builder(field)] mut attachments: Vec<NoteAttachment>,
84 sender: AccountId,
85 #[builder(name = mint_storage)] storage: MintNoteStorage,
86 serial_number: Word,
87 ) -> Result<Self, NoteError> {
88 NetworkAccountTarget::ensure_presence_if_public(&mut attachments, storage.faucet_id())
91 .map_err(|err| {
92 NoteError::other_with_source("failed to target the MINT note at its faucet", err)
93 })?;
94
95 let attachments = NoteAttachments::new(attachments)?;
96
97 Ok(Self {
98 sender,
99 storage,
100 serial_number,
101 attachments,
102 })
103 }
104}
105
106impl MintNote {
107 pub const NUM_STORAGE_ITEMS_PRIVATE: usize = 13;
114
115 pub const MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 20;
121
122 pub const NUM_STORAGE_ITEMS: NumStorageItems = NumStorageItems::AnyOf(&[
129 NumStorageItems::Exact(Self::NUM_STORAGE_ITEMS_PRIVATE),
130 NumStorageItems::Range {
131 min: Self::MIN_NUM_STORAGE_ITEMS_PUBLIC,
132 max: MAX_NOTE_STORAGE_ITEMS,
133 },
134 ]);
135
136 pub fn script() -> NoteScript {
141 MINT_SCRIPT.clone()
142 }
143
144 pub fn script_root() -> NoteScriptRoot {
146 MINT_SCRIPT.root()
147 }
148
149 pub fn faucet_id(&self) -> AccountId {
151 self.storage.faucet_id()
152 }
153
154 pub fn sender(&self) -> AccountId {
156 self.sender
157 }
158
159 pub fn storage(&self) -> &MintNoteStorage {
161 &self.storage
162 }
163
164 pub fn serial_number(&self) -> Word {
166 self.serial_number
167 }
168
169 pub fn attachments(&self) -> &NoteAttachments {
171 &self.attachments
172 }
173}
174
175impl<S: mint_note_builder::State> MintNoteBuilder<S> {
179 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
181 self.attachments.push(attachment.into());
182 self
183 }
184
185 pub fn attachments(
187 mut self,
188 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
189 ) -> Self {
190 self.attachments.extend(attachments.into_iter().map(Into::into));
191 self
192 }
193}
194
195impl<S: mint_note_builder::State> MintNoteBuilder<S>
196where
197 S::SerialNumber: mint_note_builder::IsUnset,
198{
199 pub fn generate_serial_number(
201 self,
202 rng: &mut impl FeltRng,
203 ) -> MintNoteBuilder<mint_note_builder::SetSerialNumber<S>> {
204 self.serial_number(rng.draw_word())
205 }
206}
207
208impl From<MintNote> for Note {
212 fn from(note: MintNote) -> Self {
213 let faucet_id = note.storage.faucet_id();
216 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
217 .with_tag(NoteTag::with_account_target(faucet_id));
218 let recipient = NoteRecipient::new(
219 note.serial_number,
220 MintNote::script(),
221 NoteStorage::from(note.storage),
222 );
223
224 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum MintNoteStorage {
245 Private {
246 recipient_digest: Word,
247 asset: Asset,
248 tag: NoteTag,
249 },
250 Public {
251 recipient: NoteRecipient,
252 asset: Asset,
253 tag: NoteTag,
254 },
255}
256
257impl MintNoteStorage {
258 pub fn new_private(recipient_digest: Word, asset: impl Into<Asset>, tag: NoteTag) -> Self {
260 Self::Private {
261 recipient_digest,
262 asset: asset.into(),
263 tag,
264 }
265 }
266
267 pub fn new_public(
269 recipient: NoteRecipient,
270 asset: impl Into<Asset>,
271 tag: NoteTag,
272 ) -> Result<Self, NoteError> {
273 let total_storage_items =
274 MintNote::MIN_NUM_STORAGE_ITEMS_PUBLIC + recipient.storage().num_items() as usize;
275
276 if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
277 return Err(NoteError::TooManyStorageItems(total_storage_items));
278 }
279
280 Ok(Self::Public { recipient, asset: asset.into(), tag })
281 }
282
283 pub fn asset(&self) -> Asset {
285 match self {
286 Self::Private { asset, .. } | Self::Public { asset, .. } => *asset,
287 }
288 }
289
290 pub fn faucet_id(&self) -> AccountId {
292 self.asset().faucet_id()
293 }
294}
295
296impl From<MintNoteStorage> for NoteStorage {
297 fn from(mint_storage: MintNoteStorage) -> Self {
298 match mint_storage {
299 MintNoteStorage::Private { recipient_digest, asset, tag } => {
300 let mut storage_values = Vec::with_capacity(MintNote::NUM_STORAGE_ITEMS_PRIVATE);
301 storage_values.extend_from_slice(recipient_digest.as_elements());
302 storage_values.extend_from_slice(&asset.as_elements());
303 storage_values.push(tag.into());
304 NoteStorage::new(storage_values)
305 .expect("number of storage items should not exceed max storage items")
306 },
307 MintNoteStorage::Public { recipient, asset, tag } => {
308 let mut storage_values = Vec::new();
309 storage_values.extend_from_slice(recipient.script().root().as_elements());
310 storage_values.extend_from_slice(recipient.serial_num().as_elements());
311 storage_values.extend_from_slice(&asset.as_elements());
312 storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
315 storage_values.extend_from_slice(recipient.storage().items());
316 NoteStorage::new(storage_values)
317 .expect("number of storage items should not exceed max storage items")
318 },
319 }
320 }
321}
322
323impl NoteConsumptionCost for MintNote {
327 fn consumption_cycles() -> u32 {
328 MINT_CONSUMPTION_CYCLES
329 }
330
331 fn created_notes() -> Vec<NoteScriptRoot> {
334 vec![P2idNote::script_root()]
335 }
336}
337
338#[cfg(test)]
342mod tests {
343 use miden_protocol::account::AccountType;
344 use miden_protocol::asset::FungibleAsset;
345 use miden_protocol::crypto::rand::RandomCoin;
346
347 use super::*;
348 use crate::note::{NetworkNoteExt, NoteExecutionHint};
349
350 fn faucet() -> AccountId {
351 AccountId::builder().account_type(AccountType::Public).build_with_seed([1; 32])
352 }
353
354 fn private_faucet() -> AccountId {
355 AccountId::builder().account_type(AccountType::Private).build_with_seed([1; 32])
356 }
357
358 fn owner() -> AccountId {
359 AccountId::builder().account_type(AccountType::Private).build_with_seed([2; 32])
360 }
361
362 fn build_mint_note(faucet_id: AccountId) -> MintNote {
363 let asset = FungibleAsset::new(faucet_id, 50).unwrap();
364 let mut rng = RandomCoin::new(Word::empty());
365 MintNote::builder()
366 .sender(owner())
367 .mint_storage(MintNoteStorage::new_private(Word::empty(), asset, NoteTag::default()))
368 .generate_serial_number(&mut rng)
369 .build()
370 .unwrap()
371 }
372
373 #[test]
377 fn builder_builds_public_mint_note() {
378 let mint_note = build_mint_note(faucet());
379
380 assert_eq!(mint_note.faucet_id(), faucet());
381 assert_eq!(mint_note.sender(), owner());
382 assert_eq!(mint_note.attachments().num_attachments(), 1);
383
384 let note = Note::from(mint_note);
385 assert_eq!(note.metadata().note_type(), NoteType::Public);
386 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet()));
387 assert_eq!(note.assets().num_assets(), 0);
388 assert!(note.is_network_note());
389
390 let target = NetworkAccountTarget::try_from(note.attachments()).unwrap();
391 assert_eq!(target.target_id(), faucet());
392 assert_eq!(target.execution_hint(), NoteExecutionHint::Always);
393 }
394
395 #[test]
398 fn builder_omits_network_target_for_private_faucet() {
399 let mint_note = build_mint_note(private_faucet());
400
401 assert_eq!(mint_note.attachments().num_attachments(), 0);
402
403 let note = Note::from(mint_note);
404 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(private_faucet()));
405 assert!(!note.is_network_note());
406 }
407}