miden_standards/note/
owner_action.rs1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
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;
24
25const OWNER_ACTION_SCRIPT_PATH: &str = "::miden::standards::notes::owner_action::main";
30
31static OWNER_ACTION_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
33 let standards_lib = StandardsLib::default();
34 let path = Path::new(OWNER_ACTION_SCRIPT_PATH);
35 NoteScript::from_library_reference(standards_lib.as_ref(), path)
36 .expect("Standards library contains OWNER_ACTION note script procedure")
37});
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum OwnerAction {
51 TransferOwnership { new_owner: Option<AccountId> },
55 AcceptOwnership,
57 RenounceOwnership,
60}
61
62impl OwnerAction {
63 const SELECTOR_TRANSFER_OWNERSHIP: u8 = 0;
68 const SELECTOR_ACCEPT_OWNERSHIP: u8 = 1;
69 const SELECTOR_RENOUNCE_OWNERSHIP: u8 = 2;
70
71 fn to_storage_values(self) -> Vec<Felt> {
73 match self {
74 OwnerAction::TransferOwnership { new_owner } => {
75 let (suffix, prefix) = match new_owner {
78 Some(id) => (id.suffix(), id.prefix().as_felt()),
79 None => (Felt::ZERO, Felt::ZERO),
80 };
81 vec![Felt::from(Self::SELECTOR_TRANSFER_OWNERSHIP), suffix, prefix]
82 },
83 OwnerAction::AcceptOwnership => {
84 vec![Felt::from(Self::SELECTOR_ACCEPT_OWNERSHIP)]
85 },
86 OwnerAction::RenounceOwnership => {
87 vec![Felt::from(Self::SELECTOR_RENOUNCE_OWNERSHIP)]
88 },
89 }
90 }
91}
92
93impl From<OwnerAction> for NoteStorage {
94 fn from(action: OwnerAction) -> Self {
95 NoteStorage::new(action.to_storage_values())
96 .expect("number of storage items should not exceed max storage items")
97 }
98}
99
100#[derive(Debug, Clone)]
119pub struct OwnerActionNote {
120 sender: AccountId,
121 account: AccountId,
122 action: OwnerAction,
123 serial_number: Word,
124 attachments: NoteAttachments,
125}
126
127#[bon::bon]
128impl OwnerActionNote {
129 #[builder]
136 pub fn new(
137 #[builder(field)] attachments: Vec<NoteAttachment>,
138 sender: AccountId,
139 account: AccountId,
140 action: OwnerAction,
141 serial_number: Word,
142 ) -> Result<Self, NoteError> {
143 let attachments = NoteAttachments::new(attachments)?;
144
145 Ok(Self {
146 sender,
147 account,
148 action,
149 serial_number,
150 attachments,
151 })
152 }
153}
154
155impl OwnerActionNote {
156 pub const MAX_NUM_STORAGE_ITEMS: usize = 3;
164
165 pub fn script() -> NoteScript {
170 OWNER_ACTION_SCRIPT.clone()
171 }
172
173 pub fn script_root() -> NoteScriptRoot {
175 OWNER_ACTION_SCRIPT.root()
176 }
177
178 pub fn sender(&self) -> AccountId {
180 self.sender
181 }
182
183 pub fn account(&self) -> AccountId {
185 self.account
186 }
187
188 pub fn action(&self) -> OwnerAction {
190 self.action
191 }
192
193 pub fn serial_number(&self) -> Word {
195 self.serial_number
196 }
197
198 pub fn attachments(&self) -> &NoteAttachments {
200 &self.attachments
201 }
202}
203
204impl<S: owner_action_note_builder::State> OwnerActionNoteBuilder<S> {
208 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
210 self.attachments.push(attachment.into());
211 self
212 }
213
214 pub fn attachments(
216 mut self,
217 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
218 ) -> Self {
219 self.attachments.extend(attachments.into_iter().map(Into::into));
220 self
221 }
222}
223
224impl<S: owner_action_note_builder::State> OwnerActionNoteBuilder<S>
225where
226 S::SerialNumber: owner_action_note_builder::IsUnset,
227{
228 pub fn generate_serial_number(
230 self,
231 rng: &mut impl FeltRng,
232 ) -> OwnerActionNoteBuilder<owner_action_note_builder::SetSerialNumber<S>> {
233 self.serial_number(rng.draw_word())
234 }
235}
236
237impl From<OwnerActionNote> for Note {
241 fn from(note: OwnerActionNote) -> Self {
242 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
245 .with_tag(NoteTag::with_account_target(note.account));
246 let recipient = NoteRecipient::new(
247 note.serial_number,
248 OwnerActionNote::script(),
249 NoteStorage::from(note.action),
250 );
251
252 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
253 }
254}
255
256#[cfg(test)]
260mod tests {
261 use miden_protocol::account::AccountType;
262 use miden_protocol::crypto::rand::RandomCoin;
263
264 use super::*;
265
266 fn account_id(seed: u8) -> AccountId {
267 AccountId::builder()
268 .account_type(AccountType::Public)
269 .build_with_seed([seed; 32])
270 }
271
272 #[test]
274 fn builder_builds_owner_action_note() {
275 let mut rng = RandomCoin::new(Word::empty());
276 let managed = account_id(1);
277 let owner = account_id(2);
278 let new_owner = account_id(3);
279
280 let note = OwnerActionNote::builder()
281 .sender(owner)
282 .account(managed)
283 .action(OwnerAction::TransferOwnership { new_owner: Some(new_owner) })
284 .generate_serial_number(&mut rng)
285 .build()
286 .unwrap();
287
288 assert_eq!(note.sender(), owner);
289 assert_eq!(note.account(), managed);
290
291 let note = Note::from(note);
292 assert_eq!(note.metadata().note_type(), NoteType::Public);
293 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(managed));
294 assert_eq!(note.assets().num_assets(), 0);
295 }
296
297 #[test]
299 fn transfer_ownership_storage_layout() {
300 let new_owner = account_id(3);
301 let storage =
302 NoteStorage::from(OwnerAction::TransferOwnership { new_owner: Some(new_owner) });
303
304 assert_eq!(
305 storage.items(),
306 &[
307 Felt::from(OwnerAction::SELECTOR_TRANSFER_OWNERSHIP),
308 new_owner.suffix(),
309 new_owner.prefix().as_felt(),
310 ]
311 );
312 }
313
314 #[test]
316 fn cancel_transfer_ownership_storage_layout() {
317 let storage = NoteStorage::from(OwnerAction::TransferOwnership { new_owner: None });
318
319 assert_eq!(
320 storage.items(),
321 &[Felt::from(OwnerAction::SELECTOR_TRANSFER_OWNERSHIP), Felt::ZERO, Felt::ZERO]
322 );
323 }
324
325 #[test]
327 fn accept_and_renounce_storage_layout() {
328 let accept = NoteStorage::from(OwnerAction::AcceptOwnership);
329 assert_eq!(accept.items(), &[Felt::from(OwnerAction::SELECTOR_ACCEPT_OWNERSHIP)]);
330
331 let renounce = NoteStorage::from(OwnerAction::RenounceOwnership);
332 assert_eq!(renounce.items(), &[Felt::from(OwnerAction::SELECTOR_RENOUNCE_OWNERSHIP)]);
333 }
334}