Skip to main content

miden_standards/note/
network_account_target.rs

1use 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// NETWORK ACCOUNT TARGET
15// ================================================================================================
16
17/// A [`NoteAttachment`] for notes targeted at network accounts.
18///
19/// It can be encoded to and from a [`NoteAttachmentContent::Word`] with the following layout:
20///
21/// ```text
22/// - 0th felt: [target_id_suffix (56 bits) | 8 zero bits]
23/// - 1st felt: [target_id_prefix (64 bits)]
24/// - 2nd felt: [24 zero bits | exec_hint_payload (32 bits) | exec_hint_tag (8 bits)]
25/// - 3rd felt: [64 zero bits]
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct NetworkAccountTarget {
29    target_id: AccountId,
30    exec_hint: NoteExecutionHint,
31}
32
33impl NetworkAccountTarget {
34    // CONSTANTS
35    // --------------------------------------------------------------------------------------------
36
37    /// The standardized scheme of [`NetworkAccountTarget`] attachments.
38    pub const ATTACHMENT_SCHEME: NoteAttachmentScheme =
39        WellKnownNoteAttachment::NetworkAccountTarget.attachment_scheme();
40
41    // CONSTRUCTORS
42    // --------------------------------------------------------------------------------------------
43
44    /// Creates a new [`NetworkAccountTarget`] from the provided parts.
45    ///
46    /// # Errors
47    ///
48    /// Returns an error if:
49    /// - the provided `target_id` does not have
50    ///   [`AccountStorageMode::Network`](miden_protocol::account::AccountStorageMode::Network).
51    pub fn new(
52        target_id: AccountId,
53        exec_hint: NoteExecutionHint,
54    ) -> Result<Self, NetworkAccountTargetError> {
55        // TODO: Once AccountStorageMode::Network is removed, this should check is_public.
56        if !target_id.is_network() {
57            return Err(NetworkAccountTargetError::TargetNotNetwork(target_id));
58        }
59
60        Ok(Self { target_id, exec_hint })
61    }
62
63    // ACCESSORS
64    // --------------------------------------------------------------------------------------------
65
66    /// Returns the [`AccountId`] at which the note is targeted.
67    pub fn target_id(&self) -> AccountId {
68        self.target_id
69    }
70
71    /// Returns the [`NoteExecutionHint`] of the note.
72    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// NETWORK ACCOUNT TARGET ERROR
120// ================================================================================================
121
122#[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// TESTS
143// ================================================================================================
144
145#[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}