use alloc::vec::Vec;
use miden_protocol::account::{AccountId, AccountProcedureRoot};
use miden_protocol::assembly::Path;
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteRecipient,
NoteScript,
NoteScriptRoot,
NoteStorage,
NoteTag,
NoteType,
PartialNoteMetadata,
};
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use crate::StandardsLib;
const FAUCET_POLICY_ACTION_SCRIPT_PATH: &str =
"::miden::standards::notes::faucet_policy_action::main";
static FAUCET_POLICY_ACTION_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
let standards_lib = StandardsLib::default();
let path = Path::new(FAUCET_POLICY_ACTION_SCRIPT_PATH);
NoteScript::from_library_reference(standards_lib.as_ref(), path)
.expect("Standards library contains FAUCET_POLICY_ACTION note script procedure")
});
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FaucetPolicyAction {
SetMintPolicy { policy_root: AccountProcedureRoot },
SetBurnPolicy { policy_root: AccountProcedureRoot },
SetSendPolicy { policy_root: AccountProcedureRoot },
SetReceivePolicy { policy_root: AccountProcedureRoot },
}
impl FaucetPolicyAction {
const SELECTOR_SET_MINT_POLICY: u8 = 0;
const SELECTOR_SET_BURN_POLICY: u8 = 1;
const SELECTOR_SET_SEND_POLICY: u8 = 2;
const SELECTOR_SET_RECEIVE_POLICY: u8 = 3;
fn parts(self) -> (u8, AccountProcedureRoot) {
match self {
FaucetPolicyAction::SetMintPolicy { policy_root } => {
(Self::SELECTOR_SET_MINT_POLICY, policy_root)
},
FaucetPolicyAction::SetBurnPolicy { policy_root } => {
(Self::SELECTOR_SET_BURN_POLICY, policy_root)
},
FaucetPolicyAction::SetSendPolicy { policy_root } => {
(Self::SELECTOR_SET_SEND_POLICY, policy_root)
},
FaucetPolicyAction::SetReceivePolicy { policy_root } => {
(Self::SELECTOR_SET_RECEIVE_POLICY, policy_root)
},
}
}
fn to_storage_values(self) -> Vec<Felt> {
let (selector, policy_root) = self.parts();
let mut values = Vec::with_capacity(FaucetPolicyActionNote::NUM_STORAGE_ITEMS);
values.push(Felt::from(selector));
values.extend_from_slice(policy_root.as_word().as_elements());
values
}
}
impl From<FaucetPolicyAction> for NoteStorage {
fn from(action: FaucetPolicyAction) -> Self {
NoteStorage::new(action.to_storage_values())
.expect("number of storage items should not exceed max storage items")
}
}
#[derive(Debug, Clone)]
pub struct FaucetPolicyActionNote {
sender: AccountId,
account: AccountId,
action: FaucetPolicyAction,
serial_number: Word,
attachments: NoteAttachments,
}
#[bon::bon]
impl FaucetPolicyActionNote {
#[builder]
pub fn new(
#[builder(field)] attachments: Vec<NoteAttachment>,
sender: AccountId,
account: AccountId,
action: FaucetPolicyAction,
serial_number: Word,
) -> Result<Self, NoteError> {
let attachments = NoteAttachments::new(attachments)?;
Ok(Self {
sender,
account,
action,
serial_number,
attachments,
})
}
}
impl FaucetPolicyActionNote {
pub const NUM_STORAGE_ITEMS: usize = 5;
pub fn script() -> NoteScript {
FAUCET_POLICY_ACTION_SCRIPT.clone()
}
pub fn script_root() -> NoteScriptRoot {
FAUCET_POLICY_ACTION_SCRIPT.root()
}
pub fn sender(&self) -> AccountId {
self.sender
}
pub fn account(&self) -> AccountId {
self.account
}
pub fn action(&self) -> FaucetPolicyAction {
self.action
}
pub fn serial_number(&self) -> Word {
self.serial_number
}
pub fn attachments(&self) -> &NoteAttachments {
&self.attachments
}
}
impl<S: faucet_policy_action_note_builder::State> FaucetPolicyActionNoteBuilder<S> {
pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
self.attachments.push(attachment.into());
self
}
pub fn attachments(
mut self,
attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
) -> Self {
self.attachments.extend(attachments.into_iter().map(Into::into));
self
}
}
impl<S: faucet_policy_action_note_builder::State> FaucetPolicyActionNoteBuilder<S>
where
S::SerialNumber: faucet_policy_action_note_builder::IsUnset,
{
pub fn generate_serial_number(
self,
rng: &mut impl FeltRng,
) -> FaucetPolicyActionNoteBuilder<faucet_policy_action_note_builder::SetSerialNumber<S>> {
self.serial_number(rng.draw_word())
}
}
impl From<FaucetPolicyActionNote> for Note {
fn from(note: FaucetPolicyActionNote) -> Self {
let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
.with_tag(NoteTag::with_account_target(note.account));
let recipient = NoteRecipient::new(
note.serial_number,
FaucetPolicyActionNote::script(),
NoteStorage::from(note.action),
);
Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
}
}
#[cfg(test)]
mod tests {
use miden_protocol::account::AccountType;
use miden_protocol::crypto::rand::RandomCoin;
use super::*;
fn account_id(seed: u8) -> AccountId {
AccountId::builder()
.account_type(AccountType::Public)
.build_with_seed([seed; 32])
}
fn policy_root(seed: u32) -> AccountProcedureRoot {
AccountProcedureRoot::from_raw(Word::from([seed, seed + 1, seed + 2, seed + 3]))
}
#[test]
fn builder_builds_faucet_policy_action_note() {
let mut rng = RandomCoin::new(Word::empty());
let faucet = account_id(1);
let sender = account_id(2);
let note = FaucetPolicyActionNote::builder()
.sender(sender)
.account(faucet)
.action(FaucetPolicyAction::SetMintPolicy { policy_root: policy_root(10) })
.generate_serial_number(&mut rng)
.build()
.unwrap();
assert_eq!(note.sender(), sender);
assert_eq!(note.account(), faucet);
let note = Note::from(note);
assert_eq!(note.metadata().note_type(), NoteType::Public);
assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet));
assert_eq!(note.assets().num_assets(), 0);
}
#[test]
fn storage_layout() {
let root = policy_root(10);
let cases = [
(
FaucetPolicyAction::SetMintPolicy { policy_root: root },
FaucetPolicyAction::SELECTOR_SET_MINT_POLICY,
),
(
FaucetPolicyAction::SetBurnPolicy { policy_root: root },
FaucetPolicyAction::SELECTOR_SET_BURN_POLICY,
),
(
FaucetPolicyAction::SetSendPolicy { policy_root: root },
FaucetPolicyAction::SELECTOR_SET_SEND_POLICY,
),
(
FaucetPolicyAction::SetReceivePolicy { policy_root: root },
FaucetPolicyAction::SELECTOR_SET_RECEIVE_POLICY,
),
];
for (action, selector) in cases {
let storage = NoteStorage::from(action);
let mut expected = alloc::vec![Felt::from(selector)];
expected.extend_from_slice(root.as_word().as_elements());
assert_eq!(storage.items(), expected.as_slice());
assert_eq!(storage.items().len(), FaucetPolicyActionNote::NUM_STORAGE_ITEMS);
}
}
}