miden_standards/note/config/
network_account_config.rs1use 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::transaction::TransactionScriptRoot;
21use miden_protocol::utils::sync::LazyLock;
22use miden_protocol::{Felt, Word};
23
24use crate::StandardsLib;
25use crate::note::NetworkAccountTarget;
26use crate::note::costs::{NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES, NoteConsumptionCost};
27
28const NETWORK_ACCOUNT_CONFIG_SCRIPT_PATH: &str =
33 "::miden::standards::notes::network_account_config::main";
34
35static NETWORK_ACCOUNT_CONFIG_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
37 let standards_lib = StandardsLib::default();
38 let path = Path::new(NETWORK_ACCOUNT_CONFIG_SCRIPT_PATH);
39 NoteScript::from_package_reference(standards_lib.as_ref(), path)
40 .expect("Standards library contains NETWORK_ACCOUNT_CONFIG note script procedure")
41});
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum NetworkAccountConfig {
61 AddAllowedNoteScript { script_root: NoteScriptRoot },
63 RemoveAllowedNoteScript { script_root: NoteScriptRoot },
65 AddAllowedTxScript { script_root: TransactionScriptRoot },
67 RemoveAllowedTxScript { script_root: TransactionScriptRoot },
69 AddAllowedFeePolicy { policy_root: AccountProcedureRoot },
71 RemoveAllowedFeePolicy { policy_root: AccountProcedureRoot },
73}
74
75impl NetworkAccountConfig {
76 const VARIANT_ADD_ALLOWED_NOTE_SCRIPT: u8 = 0;
82 const VARIANT_REMOVE_ALLOWED_NOTE_SCRIPT: u8 = 1;
83 const VARIANT_ADD_ALLOWED_TX_SCRIPT: u8 = 2;
84 const VARIANT_REMOVE_ALLOWED_TX_SCRIPT: u8 = 3;
85 const VARIANT_ADD_ALLOWED_FEE_POLICY: u8 = 4;
86 const VARIANT_REMOVE_ALLOWED_FEE_POLICY: u8 = 5;
87
88 fn parts(self) -> (u8, Word) {
90 match self {
91 NetworkAccountConfig::AddAllowedNoteScript { script_root } => {
92 (Self::VARIANT_ADD_ALLOWED_NOTE_SCRIPT, script_root.as_word())
93 },
94 NetworkAccountConfig::RemoveAllowedNoteScript { script_root } => {
95 (Self::VARIANT_REMOVE_ALLOWED_NOTE_SCRIPT, script_root.as_word())
96 },
97 NetworkAccountConfig::AddAllowedTxScript { script_root } => {
98 (Self::VARIANT_ADD_ALLOWED_TX_SCRIPT, script_root.as_word())
99 },
100 NetworkAccountConfig::RemoveAllowedTxScript { script_root } => {
101 (Self::VARIANT_REMOVE_ALLOWED_TX_SCRIPT, script_root.as_word())
102 },
103 NetworkAccountConfig::AddAllowedFeePolicy { policy_root } => {
104 (Self::VARIANT_ADD_ALLOWED_FEE_POLICY, policy_root.as_word())
105 },
106 NetworkAccountConfig::RemoveAllowedFeePolicy { policy_root } => {
107 (Self::VARIANT_REMOVE_ALLOWED_FEE_POLICY, policy_root.as_word())
108 },
109 }
110 }
111
112 fn to_storage_values(self) -> Vec<Felt> {
114 let (variant, script_root) = self.parts();
115 let mut values = Vec::with_capacity(NetworkAccountConfigNote::NUM_STORAGE_ITEMS);
116 values.extend_from_slice(script_root.as_elements());
117 values.push(Felt::from(variant));
118 values
119 }
120}
121
122impl From<NetworkAccountConfig> for NoteStorage {
123 fn from(config: NetworkAccountConfig) -> Self {
124 NoteStorage::new(config.to_storage_values())
125 .expect("number of storage items should not exceed max storage items")
126 }
127}
128
129#[derive(Debug, Clone)]
153pub struct NetworkAccountConfigNote {
154 sender: AccountId,
155 target: AccountId,
156 config: NetworkAccountConfig,
157 serial_number: Word,
158 attachments: NoteAttachments,
159}
160
161#[bon::bon]
162impl NetworkAccountConfigNote {
163 #[builder]
176 pub fn new(
177 #[builder(field)] mut attachments: Vec<NoteAttachment>,
178 sender: AccountId,
179 target: AccountId,
180 config: NetworkAccountConfig,
181 serial_number: Word,
182 ) -> Result<Self, NoteError> {
183 NetworkAccountTarget::ensure_presence(&mut attachments, target).map_err(|err| {
185 NoteError::other_with_source("failed to bind the note to its target account", err)
186 })?;
187
188 let attachments = NoteAttachments::new(attachments)?;
189
190 Ok(Self {
191 sender,
192 target,
193 config,
194 serial_number,
195 attachments,
196 })
197 }
198}
199
200impl NetworkAccountConfigNote {
201 pub const NUM_STORAGE_ITEMS: usize = 5;
207
208 pub fn script() -> NoteScript {
213 NETWORK_ACCOUNT_CONFIG_SCRIPT.clone()
214 }
215
216 pub fn script_root() -> NoteScriptRoot {
218 NETWORK_ACCOUNT_CONFIG_SCRIPT.root()
219 }
220
221 pub fn sender(&self) -> AccountId {
223 self.sender
224 }
225
226 pub fn target(&self) -> AccountId {
228 self.target
229 }
230
231 pub fn config(&self) -> NetworkAccountConfig {
233 self.config
234 }
235
236 pub fn serial_number(&self) -> Word {
238 self.serial_number
239 }
240
241 pub fn attachments(&self) -> &NoteAttachments {
243 &self.attachments
244 }
245}
246
247impl<S: network_account_config_note_builder::State> NetworkAccountConfigNoteBuilder<S> {
251 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
253 self.attachments.push(attachment.into());
254 self
255 }
256
257 pub fn attachments(
259 mut self,
260 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
261 ) -> Self {
262 self.attachments.extend(attachments.into_iter().map(Into::into));
263 self
264 }
265}
266
267impl<S: network_account_config_note_builder::State> NetworkAccountConfigNoteBuilder<S>
268where
269 S::SerialNumber: network_account_config_note_builder::IsUnset,
270{
271 pub fn generate_serial_number(
273 self,
274 rng: &mut impl FeltRng,
275 ) -> NetworkAccountConfigNoteBuilder<network_account_config_note_builder::SetSerialNumber<S>>
276 {
277 self.serial_number(rng.draw_word())
278 }
279}
280
281impl From<NetworkAccountConfigNote> for Note {
285 fn from(note: NetworkAccountConfigNote) -> Self {
286 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
289 .with_tag(NoteTag::with_account_target(note.target));
290 let recipient = NoteRecipient::new(
291 note.serial_number,
292 NetworkAccountConfigNote::script(),
293 NoteStorage::from(note.config),
294 );
295
296 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
297 }
298}
299
300impl NoteConsumptionCost for NetworkAccountConfigNote {
304 fn consumption_cycles() -> u32 {
305 NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES
306 }
307}
308
309#[cfg(test)]
313mod tests {
314 use alloc::vec::Vec;
315
316 use miden_protocol::account::AccountType;
317 use miden_protocol::crypto::rand::RandomCoin;
318
319 use super::*;
320
321 fn account_id(seed: u8) -> AccountId {
322 AccountId::builder()
323 .account_type(AccountType::Public)
324 .build_with_seed([seed; 32])
325 }
326
327 fn note_root(seed: u32) -> NoteScriptRoot {
328 NoteScriptRoot::from_array([seed, seed + 1, seed + 2, seed + 3])
329 }
330
331 fn tx_root(seed: u32) -> TransactionScriptRoot {
332 TransactionScriptRoot::from_raw(Word::from([seed, seed + 1, seed + 2, seed + 3]))
333 }
334
335 fn policy_root(seed: u32) -> AccountProcedureRoot {
336 AccountProcedureRoot::from_raw(Word::from([seed, seed + 1, seed + 2, seed + 3]))
337 }
338
339 #[test]
341 fn builder_builds_allowlist_config_note() {
342 let mut rng = RandomCoin::new(Word::empty());
343 let account = account_id(1);
344 let sender = account_id(2);
345
346 let note = NetworkAccountConfigNote::builder()
347 .sender(sender)
348 .target(account)
349 .config(NetworkAccountConfig::AddAllowedNoteScript { script_root: note_root(10) })
350 .generate_serial_number(&mut rng)
351 .build()
352 .unwrap();
353
354 assert_eq!(note.sender(), sender);
355 assert_eq!(note.target(), account);
356
357 let note = Note::from(note);
358 assert_eq!(note.metadata().note_type(), NoteType::Public);
359 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(account));
360 assert_eq!(note.assets().num_assets(), 0);
361
362 let target = NetworkAccountTarget::try_from(note.attachments())
364 .expect("note must carry a network account target attachment");
365 assert_eq!(target.target_id(), account);
366 }
367
368 #[test]
370 fn storage_layout() {
371 let note_root = note_root(10);
372 let tx_root = tx_root(20);
373 let policy_root = policy_root(30);
374
375 let cases = [
376 (
377 NetworkAccountConfig::AddAllowedNoteScript { script_root: note_root },
378 NetworkAccountConfig::VARIANT_ADD_ALLOWED_NOTE_SCRIPT,
379 note_root.as_word(),
380 ),
381 (
382 NetworkAccountConfig::RemoveAllowedNoteScript { script_root: note_root },
383 NetworkAccountConfig::VARIANT_REMOVE_ALLOWED_NOTE_SCRIPT,
384 note_root.as_word(),
385 ),
386 (
387 NetworkAccountConfig::AddAllowedTxScript { script_root: tx_root },
388 NetworkAccountConfig::VARIANT_ADD_ALLOWED_TX_SCRIPT,
389 tx_root.as_word(),
390 ),
391 (
392 NetworkAccountConfig::RemoveAllowedTxScript { script_root: tx_root },
393 NetworkAccountConfig::VARIANT_REMOVE_ALLOWED_TX_SCRIPT,
394 tx_root.as_word(),
395 ),
396 (
397 NetworkAccountConfig::AddAllowedFeePolicy { policy_root },
398 NetworkAccountConfig::VARIANT_ADD_ALLOWED_FEE_POLICY,
399 policy_root.as_word(),
400 ),
401 (
402 NetworkAccountConfig::RemoveAllowedFeePolicy { policy_root },
403 NetworkAccountConfig::VARIANT_REMOVE_ALLOWED_FEE_POLICY,
404 policy_root.as_word(),
405 ),
406 ];
407
408 for (action, variant, root_word) in cases {
409 let storage = NoteStorage::from(action);
410 let mut expected = Vec::from(root_word.as_elements());
411 expected.push(Felt::from(variant));
412 assert_eq!(storage.items(), expected.as_slice());
413 assert_eq!(storage.items().len(), NetworkAccountConfigNote::NUM_STORAGE_ITEMS);
414 }
415 }
416
417 #[test]
420 fn script_root_is_registered_standard_note() {
421 use crate::note::StandardNote;
422
423 let standard = StandardNote::from_script_root(NetworkAccountConfigNote::script_root())
424 .expect("config note script root should be a registered standard note");
425 assert_eq!(standard.name(), "NETWORK_ACCOUNT_CONFIG");
426 }
427}