1use alloc::vec::Vec;
2
3use miden_protocol::account::{AccountId, AccountProcedureRoot};
4use miden_protocol::assembly::Path;
5use miden_protocol::crypto::rand::FeltRng;
6use miden_protocol::errors::NoteError;
7use miden_protocol::note::{
8 Note,
9 NoteAssets,
10 NoteAttachment,
11 NoteAttachments,
12 NoteRecipient,
13 NoteScript,
14 NoteScriptRoot,
15 NoteStorage,
16 NoteTag,
17 NoteType,
18 PartialNoteMetadata,
19};
20use miden_protocol::utils::sync::LazyLock;
21use miden_protocol::{Felt, Word};
22
23use crate::StandardsLib;
24use crate::note::NetworkAccountTarget;
25use crate::note::costs::{FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES, NoteConsumptionCost};
26
27const FAUCET_POLICY_CONFIG_SCRIPT_PATH: &str =
32 "::miden::standards::notes::faucet_policy_config::main";
33
34static FAUCET_POLICY_CONFIG_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
36 let standards_lib = StandardsLib::default();
37 let path = Path::new(FAUCET_POLICY_CONFIG_SCRIPT_PATH);
38 NoteScript::from_package_reference(standards_lib.as_ref(), path)
39 .expect("Standards library contains FAUCET_POLICY_CONFIG note script procedure")
40});
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum FaucetPolicyConfig {
60 SetMintPolicy { policy_root: AccountProcedureRoot },
62 SetBurnPolicy { policy_root: AccountProcedureRoot },
64 SetSendPolicy { policy_root: AccountProcedureRoot },
66 SetReceivePolicy { policy_root: AccountProcedureRoot },
68}
69
70impl FaucetPolicyConfig {
71 const SELECTOR_SET_MINT_POLICY: u8 = 0;
77 const SELECTOR_SET_BURN_POLICY: u8 = 1;
78 const SELECTOR_SET_SEND_POLICY: u8 = 2;
79 const SELECTOR_SET_RECEIVE_POLICY: u8 = 3;
80
81 fn parts(self) -> (u8, AccountProcedureRoot) {
83 match self {
84 FaucetPolicyConfig::SetMintPolicy { policy_root } => {
85 (Self::SELECTOR_SET_MINT_POLICY, policy_root)
86 },
87 FaucetPolicyConfig::SetBurnPolicy { policy_root } => {
88 (Self::SELECTOR_SET_BURN_POLICY, policy_root)
89 },
90 FaucetPolicyConfig::SetSendPolicy { policy_root } => {
91 (Self::SELECTOR_SET_SEND_POLICY, policy_root)
92 },
93 FaucetPolicyConfig::SetReceivePolicy { policy_root } => {
94 (Self::SELECTOR_SET_RECEIVE_POLICY, policy_root)
95 },
96 }
97 }
98
99 fn to_storage_values(self) -> Vec<Felt> {
101 let (selector, policy_root) = self.parts();
102 let mut values = Vec::with_capacity(FaucetPolicyConfigNote::NUM_STORAGE_ITEMS);
103 values.extend_from_slice(policy_root.as_word().as_elements());
104 values.push(Felt::from(selector));
105 values
106 }
107}
108
109impl From<FaucetPolicyConfig> for NoteStorage {
110 fn from(config: FaucetPolicyConfig) -> Self {
111 NoteStorage::new(config.to_storage_values())
112 .expect("number of storage items should not exceed max storage items")
113 }
114}
115
116#[derive(Debug, Clone)]
142pub struct FaucetPolicyConfigNote {
143 sender: AccountId,
144 target: AccountId,
145 config: FaucetPolicyConfig,
146 serial_number: Word,
147 attachments: NoteAttachments,
148}
149
150#[bon::bon]
151impl FaucetPolicyConfigNote {
152 #[builder]
163 pub fn new(
164 #[builder(field)] mut attachments: Vec<NoteAttachment>,
165 sender: AccountId,
166 target: AccountId,
167 config: FaucetPolicyConfig,
168 serial_number: Word,
169 ) -> Result<Self, NoteError> {
170 NetworkAccountTarget::ensure_presence(&mut attachments, target).map_err(|err| {
173 NoteError::other_with_source(
174 "failed to bind the FaucetPolicyConfig note to its target account",
175 err,
176 )
177 })?;
178
179 let attachments = NoteAttachments::new(attachments)?;
180
181 Ok(Self {
182 sender,
183 target,
184 config,
185 serial_number,
186 attachments,
187 })
188 }
189}
190
191impl FaucetPolicyConfigNote {
192 pub const NUM_STORAGE_ITEMS: usize = 5;
197
198 pub fn script() -> NoteScript {
203 FAUCET_POLICY_CONFIG_SCRIPT.clone()
204 }
205
206 pub fn script_root() -> NoteScriptRoot {
208 FAUCET_POLICY_CONFIG_SCRIPT.root()
209 }
210
211 pub fn sender(&self) -> AccountId {
213 self.sender
214 }
215
216 pub fn account(&self) -> AccountId {
218 self.target
219 }
220
221 pub fn config(&self) -> FaucetPolicyConfig {
223 self.config
224 }
225
226 pub fn serial_number(&self) -> Word {
228 self.serial_number
229 }
230
231 pub fn attachments(&self) -> &NoteAttachments {
233 &self.attachments
234 }
235}
236
237impl<S: faucet_policy_config_note_builder::State> FaucetPolicyConfigNoteBuilder<S> {
241 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
243 self.attachments.push(attachment.into());
244 self
245 }
246
247 pub fn attachments(
249 mut self,
250 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
251 ) -> Self {
252 self.attachments.extend(attachments.into_iter().map(Into::into));
253 self
254 }
255}
256
257impl<S: faucet_policy_config_note_builder::State> FaucetPolicyConfigNoteBuilder<S>
258where
259 S::SerialNumber: faucet_policy_config_note_builder::IsUnset,
260{
261 pub fn generate_serial_number(
263 self,
264 rng: &mut impl FeltRng,
265 ) -> FaucetPolicyConfigNoteBuilder<faucet_policy_config_note_builder::SetSerialNumber<S>> {
266 self.serial_number(rng.draw_word())
267 }
268}
269
270impl From<FaucetPolicyConfigNote> for Note {
274 fn from(note: FaucetPolicyConfigNote) -> Self {
275 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
278 .with_tag(NoteTag::with_account_target(note.target));
279 let recipient = NoteRecipient::new(
280 note.serial_number,
281 FaucetPolicyConfigNote::script(),
282 NoteStorage::from(note.config),
283 );
284
285 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
286 }
287}
288
289impl NoteConsumptionCost for FaucetPolicyConfigNote {
293 fn consumption_cycles() -> u32 {
294 FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES
295 }
296}
297
298#[cfg(test)]
302mod tests {
303 use miden_protocol::account::AccountType;
304 use miden_protocol::crypto::rand::RandomCoin;
305
306 use super::*;
307
308 fn account_id(seed: u8) -> AccountId {
309 AccountId::builder()
310 .account_type(AccountType::Public)
311 .build_with_seed([seed; 32])
312 }
313
314 fn policy_root(seed: u32) -> AccountProcedureRoot {
315 AccountProcedureRoot::from_raw(Word::from([seed, seed + 1, seed + 2, seed + 3]))
316 }
317
318 #[test]
320 fn builder_builds_faucet_policy_config_note() {
321 let mut rng = RandomCoin::new(Word::empty());
322 let faucet = account_id(1);
323 let sender = account_id(2);
324
325 let note = FaucetPolicyConfigNote::builder()
326 .sender(sender)
327 .target(faucet)
328 .config(FaucetPolicyConfig::SetMintPolicy { policy_root: policy_root(10) })
329 .generate_serial_number(&mut rng)
330 .build()
331 .unwrap();
332
333 assert_eq!(note.sender(), sender);
334 assert_eq!(note.account(), faucet);
335
336 let note = Note::from(note);
337 assert_eq!(note.metadata().note_type(), NoteType::Public);
338 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet));
339 assert_eq!(note.assets().num_assets(), 0);
340 }
341
342 #[test]
344 fn storage_layout() {
345 let root = policy_root(10);
346
347 let cases = [
348 (
349 FaucetPolicyConfig::SetMintPolicy { policy_root: root },
350 FaucetPolicyConfig::SELECTOR_SET_MINT_POLICY,
351 ),
352 (
353 FaucetPolicyConfig::SetBurnPolicy { policy_root: root },
354 FaucetPolicyConfig::SELECTOR_SET_BURN_POLICY,
355 ),
356 (
357 FaucetPolicyConfig::SetSendPolicy { policy_root: root },
358 FaucetPolicyConfig::SELECTOR_SET_SEND_POLICY,
359 ),
360 (
361 FaucetPolicyConfig::SetReceivePolicy { policy_root: root },
362 FaucetPolicyConfig::SELECTOR_SET_RECEIVE_POLICY,
363 ),
364 ];
365
366 for (action, selector) in cases {
367 let storage = NoteStorage::from(action);
368 let mut expected = Vec::from(root.as_word().as_elements());
369 expected.push(Felt::from(selector));
370 assert_eq!(storage.items(), expected.as_slice());
371 assert_eq!(storage.items().len(), FaucetPolicyConfigNote::NUM_STORAGE_ITEMS);
372 }
373 }
374}