miden_standards/note/
network_account_target.rs1use miden_protocol::Word;
2use miden_protocol::account::AccountId;
3use miden_protocol::errors::{AccountIdError, NoteError};
4use miden_protocol::note::{NoteAttachment, NoteAttachmentScheme, NoteAttachments, NoteType};
5
6use crate::note::{NoteExecutionHint, StandardNoteAttachment};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct NetworkAccountTarget {
23 target_id: AccountId,
24 exec_hint: NoteExecutionHint,
25}
26
27impl NetworkAccountTarget {
28 pub const ATTACHMENT_SCHEME: NoteAttachmentScheme =
33 StandardNoteAttachment::NetworkAccountTarget.attachment_scheme();
34
35 pub fn new(
46 target_id: AccountId,
47 exec_hint: NoteExecutionHint,
48 ) -> Result<Self, NetworkAccountTargetError> {
49 if !target_id.is_public() {
50 return Err(NetworkAccountTargetError::TargetNotPublic(target_id));
51 }
52
53 Ok(Self { target_id, exec_hint })
54 }
55
56 pub fn target_id(&self) -> AccountId {
61 self.target_id
62 }
63
64 pub fn execution_hint(&self) -> NoteExecutionHint {
66 self.exec_hint
67 }
68}
69
70impl From<NetworkAccountTarget> for NoteAttachment {
71 fn from(network_attachment: NetworkAccountTarget) -> Self {
72 let mut word = Word::empty();
73 word[0] = network_attachment.target_id.suffix();
74 word[1] = network_attachment.target_id.prefix().as_felt();
75 word[2] = network_attachment.exec_hint.into();
76
77 NoteAttachment::with_word(NetworkAccountTarget::ATTACHMENT_SCHEME, word)
78 }
79}
80
81impl TryFrom<&NoteAttachments> for NetworkAccountTarget {
82 type Error = NetworkAccountTargetError;
83
84 fn try_from(attachments: &NoteAttachments) -> Result<Self, Self::Error> {
85 let attachment = attachments
88 .find(NetworkAccountTarget::ATTACHMENT_SCHEME)
89 .ok_or_else(|| NetworkAccountTargetError::MissingAttachmentScheme)?;
90
91 Self::try_from(attachment)
92 }
93}
94impl TryFrom<&NoteAttachment> for NetworkAccountTarget {
95 type Error = NetworkAccountTargetError;
96
97 fn try_from(attachment: &NoteAttachment) -> Result<Self, Self::Error> {
98 if attachment.attachment_scheme() != Self::ATTACHMENT_SCHEME {
99 return Err(NetworkAccountTargetError::AttachmentSchemeMismatch(
100 attachment.attachment_scheme(),
101 ));
102 }
103
104 let words = attachment.content().as_words();
105 if words.len() != 1 {
106 return Err(NetworkAccountTargetError::AttachmentContentNumWordsMismatch(
107 attachment.content().num_words(),
108 ));
109 }
110 let word = words[0];
111
112 let id_suffix = word[0];
113 let id_prefix = word[1];
114 let exec_hint = word[2];
115
116 let target_id = AccountId::try_from_elements(id_suffix, id_prefix)
117 .map_err(NetworkAccountTargetError::DecodeTargetId)?;
118
119 let exec_hint = NoteExecutionHint::try_from(exec_hint.as_canonical_u64())
120 .map_err(NetworkAccountTargetError::DecodeExecutionHint)?;
121
122 NetworkAccountTarget::new(target_id, exec_hint)
123 }
124}
125
126#[derive(Debug, thiserror::Error)]
130pub enum NetworkAccountTargetError {
131 #[error("note attachments do not contain a network account target scheme")]
132 MissingAttachmentScheme,
133 #[error("target account ID must have public account type")]
134 TargetNotPublic(AccountId),
135 #[error(
136 "attachment scheme {0} did not match expected type {expected}",
137 expected = NetworkAccountTarget::ATTACHMENT_SCHEME
138 )]
139 AttachmentSchemeMismatch(NoteAttachmentScheme),
140 #[error("network account target expects attachment content with one word, got {0}")]
141 AttachmentContentNumWordsMismatch(u16),
142 #[error("failed to decode target account ID")]
143 DecodeTargetId(#[source] AccountIdError),
144 #[error("failed to decode execution hint")]
145 DecodeExecutionHint(#[source] NoteError),
146 #[error("network note must be public, but was {0:?}")]
147 NoteNotPublic(NoteType),
148}
149
150#[cfg(test)]
154mod tests {
155 use assert_matches::assert_matches;
156 use miden_protocol::account::AccountType;
157 use miden_protocol::testing::account_id::AccountIdBuilder;
158
159 use super::*;
160
161 #[test]
162 fn network_account_target_serde() -> anyhow::Result<()> {
163 let id = AccountIdBuilder::new()
164 .account_type(AccountType::Public)
165 .build_with_rng(&mut rand::rng());
166 let network_account_target = NetworkAccountTarget::new(id, NoteExecutionHint::Always)?;
167 assert_eq!(
168 network_account_target,
169 NetworkAccountTarget::try_from(&NoteAttachment::from(network_account_target))?
170 );
171
172 Ok(())
173 }
174
175 #[test]
176 fn network_account_target_fails_on_private_target_account() -> anyhow::Result<()> {
177 let id = AccountIdBuilder::new()
178 .account_type(AccountType::Private)
179 .build_with_rng(&mut rand::rng());
180 let err = NetworkAccountTarget::new(id, NoteExecutionHint::Always).unwrap_err();
181
182 assert_matches!(
183 err,
184 NetworkAccountTargetError::TargetNotPublic(account_id) if account_id == id
185 );
186
187 Ok(())
188 }
189}