use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::errors::{AccountIdError, NoteError};
use miden_protocol::note::{
NoteAttachment,
NoteAttachmentContent,
NoteAttachmentKind,
NoteAttachmentScheme,
NoteType,
};
use crate::note::{NoteExecutionHint, StandardNoteAttachment};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NetworkAccountTarget {
target_id: AccountId,
exec_hint: NoteExecutionHint,
}
impl NetworkAccountTarget {
pub const ATTACHMENT_SCHEME: NoteAttachmentScheme =
StandardNoteAttachment::NetworkAccountTarget.attachment_scheme();
pub fn new(
target_id: AccountId,
exec_hint: NoteExecutionHint,
) -> Result<Self, NetworkAccountTargetError> {
if !target_id.is_network() {
return Err(NetworkAccountTargetError::TargetNotNetwork(target_id));
}
Ok(Self { target_id, exec_hint })
}
pub fn target_id(&self) -> AccountId {
self.target_id
}
pub fn execution_hint(&self) -> NoteExecutionHint {
self.exec_hint
}
}
impl From<NetworkAccountTarget> for NoteAttachment {
fn from(network_attachment: NetworkAccountTarget) -> Self {
let mut word = Word::empty();
word[0] = network_attachment.target_id.suffix();
word[1] = network_attachment.target_id.prefix().as_felt();
word[2] = network_attachment.exec_hint.into();
NoteAttachment::new_word(NetworkAccountTarget::ATTACHMENT_SCHEME, word)
}
}
impl TryFrom<&NoteAttachment> for NetworkAccountTarget {
type Error = NetworkAccountTargetError;
fn try_from(attachment: &NoteAttachment) -> Result<Self, Self::Error> {
if attachment.attachment_scheme() != Self::ATTACHMENT_SCHEME {
return Err(NetworkAccountTargetError::AttachmentSchemeMismatch(
attachment.attachment_scheme(),
));
}
match attachment.content() {
NoteAttachmentContent::Word(word) => {
let id_suffix = word[0];
let id_prefix = word[1];
let exec_hint = word[2];
let target_id = AccountId::try_from_elements(id_suffix, id_prefix)
.map_err(NetworkAccountTargetError::DecodeTargetId)?;
let exec_hint = NoteExecutionHint::try_from(exec_hint.as_canonical_u64())
.map_err(NetworkAccountTargetError::DecodeExecutionHint)?;
NetworkAccountTarget::new(target_id, exec_hint)
},
_ => Err(NetworkAccountTargetError::AttachmentKindMismatch(
attachment.content().attachment_kind(),
)),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum NetworkAccountTargetError {
#[error("target account ID must be of type network account")]
TargetNotNetwork(AccountId),
#[error(
"attachment scheme {0} did not match expected type {expected}",
expected = NetworkAccountTarget::ATTACHMENT_SCHEME
)]
AttachmentSchemeMismatch(NoteAttachmentScheme),
#[error(
"attachment kind {0} did not match expected type {expected}",
expected = NoteAttachmentKind::Word
)]
AttachmentKindMismatch(NoteAttachmentKind),
#[error("failed to decode target account ID")]
DecodeTargetId(#[source] AccountIdError),
#[error("failed to decode execution hint")]
DecodeExecutionHint(#[source] NoteError),
#[error("network note must be public, but was {0:?}")]
NoteNotPublic(NoteType),
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use miden_protocol::account::AccountStorageMode;
use miden_protocol::testing::account_id::AccountIdBuilder;
use super::*;
#[test]
fn network_account_target_serde() -> anyhow::Result<()> {
let id = AccountIdBuilder::new()
.storage_mode(AccountStorageMode::Network)
.build_with_rng(&mut rand::rng());
let network_account_target = NetworkAccountTarget::new(id, NoteExecutionHint::Always)?;
assert_eq!(
network_account_target,
NetworkAccountTarget::try_from(&NoteAttachment::from(network_account_target))?
);
Ok(())
}
#[test]
fn network_account_target_fails_on_private_network_target_account() -> anyhow::Result<()> {
let id = AccountIdBuilder::new()
.storage_mode(AccountStorageMode::Private)
.build_with_rng(&mut rand::rng());
let err = NetworkAccountTarget::new(id, NoteExecutionHint::Always).unwrap_err();
assert_matches!(
err,
NetworkAccountTargetError::TargetNotNetwork(account_id) if account_id == id
);
Ok(())
}
}