use alloc::vec::Vec;
use super::{Account, AccountId, Felt, PartialAccount};
use crate::Word;
use crate::crypto::SequentialCommit;
use crate::errors::AccountError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountHeader {
id: AccountId,
nonce: Felt,
vault_root: Word,
storage_commitment: Word,
code_commitment: Word,
}
impl AccountHeader {
pub(crate) const VERSION_1: u8 = 1;
pub(crate) const NUM_ELEMENTS: u8 = 16;
pub(crate) const VERSION_IDX: usize = 0;
pub(crate) const NONCE_IDX: usize = 1;
pub(crate) const ID_SUFFIX_IDX: usize = 2;
pub(crate) const ID_PREFIX_IDX: usize = 3;
const VAULT_ROOT_IDX: usize = 4;
const STORAGE_COMMITMENT_IDX: usize = 8;
const CODE_COMMITMENT_IDX: usize = 12;
pub fn new(
id: AccountId,
nonce: Felt,
vault_root: Word,
storage_commitment: Word,
code_commitment: Word,
) -> Self {
Self {
id,
nonce,
vault_root,
storage_commitment,
code_commitment,
}
}
pub(crate) fn try_from_elements(elements: &[Felt]) -> Result<AccountHeader, AccountError> {
if elements.len() != Self::NUM_ELEMENTS as usize {
return Err(AccountError::UnexpectedHeaderLength { actual: elements.len() });
}
let version = elements[Self::VERSION_IDX].as_canonical_u64();
if version != u64::from(Self::VERSION_1) {
return Err(AccountError::UnsupportedAccountVersion(version));
}
let nonce = elements[Self::NONCE_IDX];
let id = AccountId::try_from_elements(
elements[Self::ID_SUFFIX_IDX],
elements[Self::ID_PREFIX_IDX],
)
.map_err(AccountError::FinalAccountHeaderIdParsingFailed)?;
let vault_root = parse_word(elements, Self::VAULT_ROOT_IDX);
let storage_commitment = parse_word(elements, Self::STORAGE_COMMITMENT_IDX);
let code_commitment = parse_word(elements, Self::CODE_COMMITMENT_IDX);
Ok(AccountHeader::new(id, nonce, vault_root, storage_commitment, code_commitment))
}
pub fn to_commitment(&self) -> Word {
<Self as SequentialCommit>::to_commitment(self)
}
pub fn id(&self) -> AccountId {
self.id
}
pub fn nonce(&self) -> Felt {
self.nonce
}
pub fn vault_root(&self) -> Word {
self.vault_root
}
pub fn storage_commitment(&self) -> Word {
self.storage_commitment
}
pub fn code_commitment(&self) -> Word {
self.code_commitment
}
pub fn to_elements(&self) -> Vec<Felt> {
<Self as SequentialCommit>::to_elements(self)
}
}
impl From<&PartialAccount> for AccountHeader {
fn from(account: &PartialAccount) -> Self {
Self {
id: account.id(),
nonce: account.nonce(),
vault_root: account.vault().root(),
storage_commitment: account.storage().commitment(),
code_commitment: account.code().commitment(),
}
}
}
impl From<&Account> for AccountHeader {
fn from(account: &Account) -> Self {
Self {
id: account.id(),
nonce: account.nonce(),
vault_root: account.vault().root(),
storage_commitment: account.storage().to_commitment(),
code_commitment: account.code().commitment(),
}
}
}
impl SequentialCommit for AccountHeader {
type Commitment = Word;
fn to_elements(&self) -> Vec<Felt> {
let mut metadata_word = Word::empty();
metadata_word[Self::VERSION_IDX] = Felt::from(Self::VERSION_1);
metadata_word[Self::NONCE_IDX] = self.nonce;
metadata_word[Self::ID_SUFFIX_IDX] = self.id.suffix();
metadata_word[Self::ID_PREFIX_IDX] = self.id.prefix().as_felt();
[
metadata_word.as_elements(),
self.vault_root.as_elements(),
self.storage_commitment.as_elements(),
self.code_commitment.as_elements(),
]
.concat()
}
}
impl Serializable for AccountHeader {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
Self::VERSION_1.write_into(target);
self.id.write_into(target);
self.nonce.write_into(target);
self.vault_root.write_into(target);
self.storage_commitment.write_into(target);
self.code_commitment.write_into(target);
}
}
impl Deserializable for AccountHeader {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let version = u8::read_from(source)?;
if version != Self::VERSION_1 {
return Err(DeserializationError::InvalidValue(format!(
"account version is {} but only version {} is supported",
version,
Self::VERSION_1,
)));
}
let id = AccountId::read_from(source)?;
let nonce = Felt::read_from(source)?;
let vault_root = Word::read_from(source)?;
let storage_commitment = Word::read_from(source)?;
let code_commitment = Word::read_from(source)?;
Ok(AccountHeader {
id,
nonce,
vault_root,
storage_commitment,
code_commitment,
})
}
}
fn parse_word(data: &[Felt], offset: usize) -> Word {
Word::try_from(&data[offset..offset + Word::NUM_ELEMENTS])
.expect("we should have sliced off exactly 4 bytes")
}
#[cfg(test)]
mod tests {
use anyhow::Context;
use assert_matches::assert_matches;
use miden_core::Felt;
use super::AccountHeader;
use crate::Word;
use crate::account::tests::build_account;
use crate::account::{AccountId, StorageSlotContent};
use crate::asset::FungibleAsset;
use crate::errors::AccountError;
use crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;
use crate::utils::serde::{Deserializable, DeserializationError, Serializable};
fn mock_header() -> anyhow::Result<AccountHeader> {
let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
.context("failed to build account ID")?;
Ok(AccountHeader::new(
id,
Felt::from(42u32),
Word::from([1, 2, 3, 4u32]),
Word::from([5, 6, 7, 8u32]),
Word::from([9, 10, 11, 12u32]),
))
}
#[rstest::rstest]
#[case::version_zero(0)]
#[case::version_two(2)]
#[case::version_exceeding_u8((1 << 8) | u32::from(AccountHeader::VERSION_1))]
fn account_header_rejects_unsupported_version(#[case] version: u32) -> anyhow::Result<()> {
let mut elements = mock_header()?.to_elements();
elements[AccountHeader::VERSION_IDX] = Felt::from(version);
let error = AccountHeader::try_from_elements(&elements)
.expect_err("header with an unsupported version should not parse");
assert_matches!(error, AccountError::UnsupportedAccountVersion(actual) => {
assert_eq!(actual, u64::from(version));
});
Ok(())
}
#[test]
fn test_serde_account_storage() {
let init_nonce = Felt::from(1_u32);
let asset_0 = FungibleAsset::mock(99);
let word = Word::from([1, 2, 3, 4u32]);
let storage_slot = StorageSlotContent::Value(word);
let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
let account_header = account.to_header();
let header_bytes = account_header.to_bytes();
let deserialized_header = AccountHeader::read_from_bytes(&header_bytes).unwrap();
assert_eq!(deserialized_header, account_header);
}
#[test]
fn account_header_deserialization_rejects_unsupported_version() {
let error = AccountHeader::read_from_bytes(&[0]).unwrap_err();
assert_matches!(error, DeserializationError::InvalidValue(message) => {
assert!(message.contains("account version is 0"));
});
}
}