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::{
5 NoteAttachment,
6 NoteAttachmentContent,
7 NoteAttachmentKind,
8 NoteAttachmentScheme,
9 NoteExecutionHint,
10};
11
12use crate::note::WellKnownNoteAttachment;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct NetworkAccountTarget {
29 target_id: AccountId,
30 exec_hint: NoteExecutionHint,
31}
32
33impl NetworkAccountTarget {
34 pub const ATTACHMENT_SCHEME: NoteAttachmentScheme =
39 WellKnownNoteAttachment::NetworkAccountTarget.attachment_scheme();
40
41 pub fn new(
52 target_id: AccountId,
53 exec_hint: NoteExecutionHint,
54 ) -> Result<Self, NetworkAccountTargetError> {
55 if !target_id.is_network() {
57 return Err(NetworkAccountTargetError::TargetNotNetwork(target_id));
58 }
59
60 Ok(Self { target_id, exec_hint })
61 }
62
63 pub fn target_id(&self) -> AccountId {
68 self.target_id
69 }
70
71 pub fn execution_hint(&self) -> NoteExecutionHint {
73 self.exec_hint
74 }
75}
76
77impl From<NetworkAccountTarget> for NoteAttachment {
78 fn from(network_attachment: NetworkAccountTarget) -> Self {
79 let mut word = Word::empty();
80 word[0] = network_attachment.target_id.suffix();
81 word[1] = network_attachment.target_id.prefix().as_felt();
82 word[2] = network_attachment.exec_hint.into();
83
84 NoteAttachment::new_word(NetworkAccountTarget::ATTACHMENT_SCHEME, word)
85 }
86}
87
88impl TryFrom<&NoteAttachment> for NetworkAccountTarget {
89 type Error = NetworkAccountTargetError;
90
91 fn try_from(attachment: &NoteAttachment) -> Result<Self, Self::Error> {
92 if attachment.attachment_scheme() != Self::ATTACHMENT_SCHEME {
93 return Err(NetworkAccountTargetError::AttachmentSchemeMismatch(
94 attachment.attachment_scheme(),
95 ));
96 }
97
98 match attachment.content() {
99 NoteAttachmentContent::Word(word) => {
100 let id_suffix = word[0];
101 let id_prefix = word[1];
102 let exec_hint = word[2];
103
104 let target_id = AccountId::try_from([id_prefix, id_suffix])
105 .map_err(NetworkAccountTargetError::DecodeTargetId)?;
106
107 let exec_hint = NoteExecutionHint::try_from(exec_hint.as_int())
108 .map_err(NetworkAccountTargetError::DecodeExecutionHint)?;
109
110 NetworkAccountTarget::new(target_id, exec_hint)
111 },
112 _ => Err(NetworkAccountTargetError::AttachmentKindMismatch(
113 attachment.content().attachment_kind(),
114 )),
115 }
116 }
117}
118
119#[derive(Debug, thiserror::Error)]
123pub enum NetworkAccountTargetError {
124 #[error("target account ID must be of type network account")]
125 TargetNotNetwork(AccountId),
126 #[error(
127 "attachment scheme {0} did not match expected type {expected}",
128 expected = NetworkAccountTarget::ATTACHMENT_SCHEME
129 )]
130 AttachmentSchemeMismatch(NoteAttachmentScheme),
131 #[error(
132 "attachment kind {0} did not match expected type {expected}",
133 expected = NoteAttachmentKind::Word
134 )]
135 AttachmentKindMismatch(NoteAttachmentKind),
136 #[error("failed to decode target account ID")]
137 DecodeTargetId(#[source] AccountIdError),
138 #[error("failed to decode execution hint")]
139 DecodeExecutionHint(#[source] NoteError),
140}
141
142#[cfg(test)]
146mod tests {
147 use assert_matches::assert_matches;
148 use miden_protocol::account::AccountStorageMode;
149 use miden_protocol::testing::account_id::AccountIdBuilder;
150
151 use super::*;
152
153 #[test]
154 fn network_account_target_serde() -> anyhow::Result<()> {
155 let id = AccountIdBuilder::new()
156 .storage_mode(AccountStorageMode::Network)
157 .build_with_rng(&mut rand::rng());
158 let network_account_target = NetworkAccountTarget::new(id, NoteExecutionHint::Always)?;
159 assert_eq!(
160 network_account_target,
161 NetworkAccountTarget::try_from(&NoteAttachment::from(network_account_target))?
162 );
163
164 Ok(())
165 }
166
167 #[test]
168 fn network_account_target_fails_on_private_network_target_account() -> anyhow::Result<()> {
169 let id = AccountIdBuilder::new()
170 .storage_mode(AccountStorageMode::Private)
171 .build_with_rng(&mut rand::rng());
172 let err = NetworkAccountTarget::new(id, NoteExecutionHint::Always).unwrap_err();
173
174 assert_matches!(
175 err,
176 NetworkAccountTargetError::TargetNotNetwork(account_id) if account_id == id
177 );
178
179 Ok(())
180 }
181}