use core::{fmt, num::TryFromIntError};
use miden_crypto::Felt;
use super::{
AccountId, ByteReader, ByteWriter, Deserializable, DeserializationError, NoteError, NoteType,
Serializable,
};
const NETWORK_EXECUTION: u8 = 0;
const LOCAL_EXECUTION: u8 = 1;
const LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED: u32 = 0xc0000000;
const PUBLIC_USECASE: u32 = 0x80000000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum NoteExecutionMode {
Network = NETWORK_EXECUTION,
Local = LOCAL_EXECUTION,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct NoteTag(u32);
impl NoteTag {
pub fn from_account_id(
account_id: AccountId,
execution: NoteExecutionMode,
) -> Result<Self, NoteError> {
match execution {
NoteExecutionMode::Local => {
let id: u64 = account_id.into();
let high_bits = (id >> 34) as u32 & 0xffff0000;
Ok(Self(high_bits | LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED))
},
NoteExecutionMode::Network => {
if !account_id.is_public() {
Err(NoteError::NetworkExecutionRequiresOnChainAccount)
} else {
let id: u64 = account_id.into();
let high_bits = (id >> 33) as u32;
Ok(Self(high_bits))
}
},
}
}
pub fn for_public_use_case(
use_case_id: u16,
payload: u16,
execution: NoteExecutionMode,
) -> Result<Self, NoteError> {
if (use_case_id >> 14) != 0 {
return Err(NoteError::InvalidNoteTagUseCase(use_case_id));
}
let execution_bits = match execution {
NoteExecutionMode::Local => PUBLIC_USECASE, NoteExecutionMode::Network => 0x40000000, };
let use_case_bits = (use_case_id as u32) << 16;
let payload_bits = payload as u32;
Ok(Self(execution_bits | use_case_bits | payload_bits))
}
pub fn for_local_use_case(use_case_id: u16, payload: u16) -> Result<Self, NoteError> {
if (use_case_id >> 14) != 0 {
return Err(NoteError::InvalidNoteTagUseCase(use_case_id));
}
let execution_bits = LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED;
let use_case_bits = (use_case_id as u32) << 16;
let payload_bits = payload as u32;
Ok(Self(execution_bits | use_case_bits | payload_bits))
}
pub fn is_single_target(&self) -> bool {
let first_2_bit = self.0 >> 30;
first_2_bit == 0b00
}
pub fn execution_hint(&self) -> NoteExecutionMode {
let first_bit = self.0 >> 31;
if first_bit == (LOCAL_EXECUTION as u32) {
NoteExecutionMode::Local
} else {
NoteExecutionMode::Network
}
}
pub fn inner(&self) -> u32 {
self.0
}
pub fn validate(&self, note_type: NoteType) -> Result<Self, NoteError> {
if self.execution_hint() == NoteExecutionMode::Network && note_type != NoteType::Public {
return Err(NoteError::NetworkExecutionRequiresPublicNote(note_type));
}
let is_public_use_case = (self.0 & 0xc0000000) == PUBLIC_USECASE;
if is_public_use_case && note_type != NoteType::Public {
Err(NoteError::PublicUseCaseRequiresPublicNote(note_type))
} else {
Ok(*self)
}
}
}
impl fmt::Display for NoteTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u32> for NoteTag {
fn from(value: u32) -> Self {
Self(value)
}
}
impl TryFrom<u64> for NoteTag {
type Error = TryFromIntError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Ok(Self(value.try_into()?))
}
}
impl TryFrom<Felt> for NoteTag {
type Error = TryFromIntError;
fn try_from(value: Felt) -> Result<Self, Self::Error> {
Ok(Self(value.as_int().try_into()?))
}
}
impl From<NoteTag> for u32 {
fn from(value: NoteTag) -> Self {
value.0
}
}
impl From<NoteTag> for u64 {
fn from(value: NoteTag) -> Self {
value.0 as u64
}
}
impl From<NoteTag> for Felt {
fn from(value: NoteTag) -> Self {
Felt::from(value.0)
}
}
impl Serializable for NoteTag {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.0.write_into(target);
}
}
impl Deserializable for NoteTag {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let tag = u32::read_from(source)?;
Ok(Self(tag))
}
}
#[cfg(test)]
mod tests {
use super::{NoteExecutionMode, NoteTag};
use crate::{
accounts::{
account_id::testing::{
ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN,
ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2,
ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1,
ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN,
ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2,
ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN,
ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER,
},
AccountId,
},
notes::NoteType,
NoteError,
};
#[test]
fn test_from_account_id() {
let off_chain_accounts = [
AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
AccountId::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(),
];
let on_chain_accounts = [
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3).unwrap(),
AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(),
];
for off_chain in off_chain_accounts {
assert!(
NoteTag::from_account_id(off_chain, NoteExecutionMode::Network).is_err(),
"Tag generation must fail if network execution and off-chain account id are mixed"
);
}
for on_chain in on_chain_accounts {
let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Network)
.expect("Tag generation must work with network exeuction and on-chain accounts");
assert!(tag.is_single_target());
assert_eq!(tag.execution_hint(), NoteExecutionMode::Network);
assert_eq!(
tag.validate(NoteType::Public),
Ok(tag),
"Network execution requires public notes"
);
assert_eq!(
tag.validate(NoteType::Private),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
);
assert_eq!(
tag.validate(NoteType::Encrypted),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
);
}
for off_chain in off_chain_accounts {
let tag = NoteTag::from_account_id(off_chain, NoteExecutionMode::Local)
.expect("Tag generation must work with network execution and off-chain account id");
assert!(!tag.is_single_target());
assert_eq!(tag.execution_hint(), NoteExecutionMode::Local);
assert_eq!(
tag.validate(NoteType::Public),
Ok(tag),
"Local execution supports public notes"
);
assert_eq!(
tag.validate(NoteType::Private),
Ok(tag),
"Local execution supports private notes"
);
assert_eq!(
tag.validate(NoteType::Encrypted),
Ok(tag),
"Local execution supports encrypted notes"
);
}
for on_chain in on_chain_accounts {
let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Local)
.expect("Tag generation must work with network exeuction and on-chain accounts");
assert!(!tag.is_single_target());
assert_eq!(tag.execution_hint(), NoteExecutionMode::Local);
assert_eq!(
tag.validate(NoteType::Public),
Ok(tag),
"Local execution supports public notes"
);
assert_eq!(
tag.validate(NoteType::Private),
Ok(tag),
"Local execution supports private notes"
);
assert_eq!(
tag.validate(NoteType::Encrypted),
Ok(tag),
"Local execution supports encrypted notes"
);
}
}
#[test]
fn test_from_account_id_values() {
let off_chain =
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap();
let on_chain =
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap();
assert_eq!(
NoteTag::from_account_id(on_chain, NoteExecutionMode::Network),
Ok(NoteTag(0b00000000_00000000_00000000_00000000))
);
assert!(NoteTag::from_account_id(off_chain, NoteExecutionMode::Network).is_err());
assert_eq!(
NoteTag::from_account_id(off_chain, NoteExecutionMode::Local),
Ok(NoteTag(0b11100100_00000000_00000000_00000000))
);
assert_eq!(
NoteTag::from_account_id(on_chain, NoteExecutionMode::Local),
Ok(NoteTag(0b11000000_00000000_00000000_00000000))
);
}
#[test]
fn test_for_public_use_case() {
let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network);
assert_eq!(tag, Ok(NoteTag(0b01000000_00000000_00000000_00000000)));
let tag = tag.unwrap();
assert_eq!(tag.validate(NoteType::Public), Ok(tag));
assert_eq!(
tag.validate(NoteType::Private),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
);
assert_eq!(
tag.validate(NoteType::Encrypted),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
);
let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network);
assert_eq!(tag, Ok(NoteTag(0b01000000_00000001_00000000_00000000)));
let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network);
assert_eq!(tag, Ok(NoteTag(0b01000000_00000000_00000000_00000001)));
let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network);
assert_eq!(tag, Ok(NoteTag(0b01100000_00000000_00000000_00000000)));
let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local);
assert_eq!(tag, Ok(NoteTag(0b10000000_00000000_00000000_00000000)));
let tag = tag.unwrap();
assert_eq!(tag.validate(NoteType::Public), Ok(tag));
assert_eq!(
tag.validate(NoteType::Private),
Err(NoteError::PublicUseCaseRequiresPublicNote(NoteType::Private))
);
assert_eq!(
tag.validate(NoteType::Encrypted),
Err(NoteError::PublicUseCaseRequiresPublicNote(NoteType::Encrypted))
);
let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local);
assert_eq!(tag, Ok(NoteTag(0b10000000_00000000_00000000_00000001)));
let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local);
assert_eq!(tag, Ok(NoteTag(0b10000000_00000001_00000000_00000000)));
let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local);
assert_eq!(tag, Ok(NoteTag(0b10100000_00000000_00000000_00000000)));
assert!(NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).is_err());
assert!(NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).is_err());
}
#[test]
fn test_for_private_use_case() {
let tag = NoteTag::for_local_use_case(0b0, 0b0);
assert_eq!(tag, Ok(NoteTag(0b11000000_00000000_00000000_00000000)));
let tag = tag.unwrap();
assert_eq!(
tag.validate(NoteType::Public),
Ok(tag),
"Local execution supports private notes"
);
assert_eq!(
tag.validate(NoteType::Private),
Ok(tag),
"Local execution supports private notes"
);
assert_eq!(
tag.validate(NoteType::Encrypted),
Ok(tag),
"Local execution supports encrypted notes"
);
let tag = NoteTag::for_local_use_case(0b0, 0b1);
assert_eq!(tag, Ok(NoteTag(0b11000000_00000000_00000000_00000001)));
let tag = NoteTag::for_local_use_case(0b1, 0b0);
assert_eq!(tag, Ok(NoteTag(0b11000000_00000001_00000000_00000000)));
let tag = NoteTag::for_local_use_case(1 << 13, 0b0);
assert_eq!(tag, Ok(NoteTag(0b11100000_00000000_00000000_00000000)));
assert!(NoteTag::for_local_use_case(1 << 15, 0b0).is_err());
assert!(NoteTag::for_local_use_case(1 << 14, 0b0).is_err());
}
#[test]
fn test_only_onchain_account_have_the_highbit_set_to_zero() {
let accounts = [
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3).unwrap(),
AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(),
AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
AccountId::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(),
AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(),
];
for acct in accounts {
let highbit = u64::from(acct) >> 63;
let onchain = highbit == 0;
assert_eq!(
acct.is_public(),
onchain,
"The account_id encoding changed, this breaks the assumptions built in the NoteTag"
);
}
}
}