1use alloc::vec::Vec;
2
3use super::{Account, AccountId, Felt, PartialAccount};
4use crate::Word;
5use crate::crypto::SequentialCommit;
6use crate::errors::AccountError;
7use crate::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct AccountHeader {
29 id: AccountId,
30 nonce: Felt,
31 vault_root: Word,
32 storage_commitment: Word,
33 code_commitment: Word,
34}
35
36impl AccountHeader {
37 pub(crate) const VERSION_1: u8 = 1;
46
47 pub(crate) const NUM_ELEMENTS: u8 = 16;
49
50 pub(crate) const VERSION_IDX: usize = 0;
52
53 pub(crate) const NONCE_IDX: usize = 1;
55
56 pub(crate) const ID_SUFFIX_IDX: usize = 2;
58
59 pub(crate) const ID_PREFIX_IDX: usize = 3;
61
62 const VAULT_ROOT_IDX: usize = 4;
64
65 const STORAGE_COMMITMENT_IDX: usize = 8;
67
68 const CODE_COMMITMENT_IDX: usize = 12;
70
71 pub fn new(
76 id: AccountId,
77 nonce: Felt,
78 vault_root: Word,
79 storage_commitment: Word,
80 code_commitment: Word,
81 ) -> Self {
82 Self {
83 id,
84 nonce,
85 vault_root,
86 storage_commitment,
87 code_commitment,
88 }
89 }
90
91 pub(crate) fn try_from_elements(elements: &[Felt]) -> Result<AccountHeader, AccountError> {
95 if elements.len() != Self::NUM_ELEMENTS as usize {
96 return Err(AccountError::UnexpectedHeaderLength { actual: elements.len() });
97 }
98
99 let version = elements[Self::VERSION_IDX].as_canonical_u64();
100 if version != u64::from(Self::VERSION_1) {
101 return Err(AccountError::UnsupportedAccountVersion(version));
102 }
103 let nonce = elements[Self::NONCE_IDX];
104 let id = AccountId::try_from_elements(
105 elements[Self::ID_SUFFIX_IDX],
106 elements[Self::ID_PREFIX_IDX],
107 )
108 .map_err(AccountError::FinalAccountHeaderIdParsingFailed)?;
109
110 let vault_root = parse_word(elements, Self::VAULT_ROOT_IDX);
111 let storage_commitment = parse_word(elements, Self::STORAGE_COMMITMENT_IDX);
112 let code_commitment = parse_word(elements, Self::CODE_COMMITMENT_IDX);
113
114 Ok(AccountHeader::new(id, nonce, vault_root, storage_commitment, code_commitment))
115 }
116
117 pub fn to_commitment(&self) -> Word {
126 <Self as SequentialCommit>::to_commitment(self)
127 }
128
129 pub fn id(&self) -> AccountId {
131 self.id
132 }
133
134 pub fn nonce(&self) -> Felt {
136 self.nonce
137 }
138
139 pub fn vault_root(&self) -> Word {
141 self.vault_root
142 }
143
144 pub fn storage_commitment(&self) -> Word {
146 self.storage_commitment
147 }
148
149 pub fn code_commitment(&self) -> Word {
151 self.code_commitment
152 }
153
154 pub fn to_elements(&self) -> Vec<Felt> {
168 <Self as SequentialCommit>::to_elements(self)
169 }
170}
171
172impl From<&PartialAccount> for AccountHeader {
173 fn from(account: &PartialAccount) -> Self {
174 Self {
175 id: account.id(),
176 nonce: account.nonce(),
177 vault_root: account.vault().root(),
178 storage_commitment: account.storage().commitment(),
179 code_commitment: account.code().commitment(),
180 }
181 }
182}
183
184impl From<&Account> for AccountHeader {
185 fn from(account: &Account) -> Self {
186 Self {
187 id: account.id(),
188 nonce: account.nonce(),
189 vault_root: account.vault().root(),
190 storage_commitment: account.storage().to_commitment(),
191 code_commitment: account.code().commitment(),
192 }
193 }
194}
195
196impl SequentialCommit for AccountHeader {
197 type Commitment = Word;
198
199 fn to_elements(&self) -> Vec<Felt> {
200 let mut metadata_word = Word::empty();
201 metadata_word[Self::VERSION_IDX] = Felt::from(Self::VERSION_1);
202 metadata_word[Self::NONCE_IDX] = self.nonce;
203 metadata_word[Self::ID_SUFFIX_IDX] = self.id.suffix();
204 metadata_word[Self::ID_PREFIX_IDX] = self.id.prefix().as_felt();
205
206 [
207 metadata_word.as_elements(),
208 self.vault_root.as_elements(),
209 self.storage_commitment.as_elements(),
210 self.code_commitment.as_elements(),
211 ]
212 .concat()
213 }
214}
215
216impl Serializable for AccountHeader {
220 fn write_into<W: ByteWriter>(&self, target: &mut W) {
221 Self::VERSION_1.write_into(target);
222 self.id.write_into(target);
223 self.nonce.write_into(target);
224 self.vault_root.write_into(target);
225 self.storage_commitment.write_into(target);
226 self.code_commitment.write_into(target);
227 }
228}
229
230impl Deserializable for AccountHeader {
231 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
232 let version = u8::read_from(source)?;
233
234 if version != Self::VERSION_1 {
235 return Err(DeserializationError::InvalidValue(format!(
236 "account version is {} but only version {} is supported",
237 version,
238 Self::VERSION_1,
239 )));
240 }
241
242 let id = AccountId::read_from(source)?;
243 let nonce = Felt::read_from(source)?;
244 let vault_root = Word::read_from(source)?;
245 let storage_commitment = Word::read_from(source)?;
246 let code_commitment = Word::read_from(source)?;
247
248 Ok(AccountHeader {
249 id,
250 nonce,
251 vault_root,
252 storage_commitment,
253 code_commitment,
254 })
255 }
256}
257
258fn parse_word(data: &[Felt], offset: usize) -> Word {
263 Word::try_from(&data[offset..offset + Word::NUM_ELEMENTS])
264 .expect("we should have sliced off exactly 4 bytes")
265}
266
267#[cfg(test)]
271mod tests {
272 use anyhow::Context;
273 use assert_matches::assert_matches;
274 use miden_core::Felt;
275
276 use super::AccountHeader;
277 use crate::Word;
278 use crate::account::tests::build_account;
279 use crate::account::{AccountId, StorageSlotContent};
280 use crate::asset::FungibleAsset;
281 use crate::errors::AccountError;
282 use crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;
283 use crate::utils::serde::{Deserializable, DeserializationError, Serializable};
284
285 fn mock_header() -> anyhow::Result<AccountHeader> {
288 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
289 .context("failed to build account ID")?;
290
291 Ok(AccountHeader::new(
292 id,
293 Felt::from(42u32),
294 Word::from([1, 2, 3, 4u32]),
295 Word::from([5, 6, 7, 8u32]),
296 Word::from([9, 10, 11, 12u32]),
297 ))
298 }
299
300 #[rstest::rstest]
301 #[case::version_zero(0)]
302 #[case::version_two(2)]
303 #[case::version_exceeding_u8((1 << 8) | u32::from(AccountHeader::VERSION_1))]
306 fn account_header_rejects_unsupported_version(#[case] version: u32) -> anyhow::Result<()> {
307 let mut elements = mock_header()?.to_elements();
308 elements[AccountHeader::VERSION_IDX] = Felt::from(version);
309
310 let error = AccountHeader::try_from_elements(&elements)
311 .expect_err("header with an unsupported version should not parse");
312
313 assert_matches!(error, AccountError::UnsupportedAccountVersion(actual) => {
314 assert_eq!(actual, u64::from(version));
315 });
316
317 Ok(())
318 }
319
320 #[test]
321 fn test_serde_account_storage() {
322 let init_nonce = Felt::from(1_u32);
323 let asset_0 = FungibleAsset::mock(99);
324 let word = Word::from([1, 2, 3, 4u32]);
325 let storage_slot = StorageSlotContent::Value(word);
326 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
327
328 let account_header = account.to_header();
329
330 let header_bytes = account_header.to_bytes();
331 let deserialized_header = AccountHeader::read_from_bytes(&header_bytes).unwrap();
332 assert_eq!(deserialized_header, account_header);
333 }
334
335 #[test]
336 fn account_header_deserialization_rejects_unsupported_version() {
337 let error = AccountHeader::read_from_bytes(&[0]).unwrap_err();
338
339 assert_matches!(error, DeserializationError::InvalidValue(message) => {
340 assert!(message.contains("account version is 0"));
341 });
342 }
343}