Skip to content

feat: implement serialization for AccountPatch and AccountVaultPatch and apply_patch#3071

Open
PhilippGackstatter wants to merge 41 commits into
nextfrom
pgackst-patch-serialization
Open

feat: implement serialization for AccountPatch and AccountVaultPatch and apply_patch#3071
PhilippGackstatter wants to merge 41 commits into
nextfrom
pgackst-patch-serialization

Conversation

@PhilippGackstatter

@PhilippGackstatter PhilippGackstatter commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Implements:

  • serialization for AccountPatch and AccountVaultPatch. The latter's serialization for fungible assets is optimized to save space, as suggested by @bobbinth.
  • Account::apply_patch API.
  • impl TryFrom<&AccountPatch> for Account
  • Adds a missing ID check in apply_delta (ensuring the delta is applied to the right account).

Needed for including AccountPatch in ExecutedTransaction and replacing AccountDelta, so part of #2630.

@PhilippGackstatter PhilippGackstatter added the pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority label Jun 9, 2026
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-patch-serialization branch 2 times, most recently from 9d69cc7 to 0a0228d Compare June 9, 2026 13:48
@PhilippGackstatter PhilippGackstatter changed the title feat: implement serialization for AccountPatch and AccountVaultPatch feat: implement serialization for AccountPatch and AccountVaultPatch and apply_patch Jun 9, 2026
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-patch-serialization branch 2 times, most recently from fa79ae2 to 1c6c264 Compare June 9, 2026 14:41
@PhilippGackstatter PhilippGackstatter marked this pull request as ready for review June 9, 2026 14:42
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-patch-serialization branch from 1c6c264 to 985197b Compare June 10, 2026 11:45

@bobbinth bobbinth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you! I left just a few comments inline - the main one is about serialization.

Comment on lines +161 to +166
pub fn is_full_state(&self) -> bool {
// TODO(code_upgrades): Change this to another detection mechanism once we have code upgrade
// support, at which point the presence of code may not be enough of an indication that a
// patch can be converted to a full account.
self.code.is_some() && self.final_nonce.is_some()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not be just self.final_nonce == 1? Basically, if the final nonce is one, this patch should contain all required info to create a new account.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, if the final nonce is one, this patch should contain all required info to create a new account.

Yes, but I think the condition is larger: If we merge patches from multiple transactions, the nonce may have advanced past 1 but still contain the code, which is necessary to convert into an Account. So writing the condition this way is less strict.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we potentially disallow merging patches with final_nonce == 1?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - I see. In that case, wouldn't be just self.code.is_some() be sufficient? Basically, a combination of self.code.is_some() and self.final_nonce.is_none() shouldn't be possible by construction, right?

Comment thread crates/miden-protocol/src/account/patch/vault.rs
Comment thread crates/miden-protocol/src/account/mod.rs Outdated
PhilippGackstatter and others added 2 commits June 11, 2026 11:44
Per review feedback, serialize removed entries as bare `AssetVaultKey`
and added entries as `Asset`, letting `Asset` own the fungible vs.
non-fungible split instead of duplicating that logic in the patch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@PhilippGackstatter

Copy link
Copy Markdown
Contributor Author

Pushed a small additional commit to implement AccountPatch::try_from(account) as it fits here conceptually and is just a few dozen lines of code.

Base automatically changed from pgackst-unified-asset-delta to next June 12, 2026 12:36

@mmagician mmagician left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

partial review for now, LGTM so far

// Code must be provided for new accounts (nonce = 1) to be able to reconstruct the full
// Account.
// Code must be provided for new accounts to be able to reconstruct the full Account.
// New accounts are usually defined with nonce 0, but here we have the post-creation

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// New accounts are usually defined with nonce 0, but here we have the post-creation
// New accounts are defined with nonce 0, but here we have the post-creation

Comment on lines +161 to +166
pub fn is_full_state(&self) -> bool {
// TODO(code_upgrades): Change this to another detection mechanism once we have code upgrade
// support, at which point the presence of code may not be enough of an indication that a
// patch can be converted to a full account.
self.code.is_some() && self.final_nonce.is_some()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we potentially disallow merging patches with final_nonce == 1?

Comment on lines +246 to +256
fn try_from(patch: &AccountPatch) -> Result<Self, Self::Error> {
if !patch.is_full_state() {
return Err(AccountError::PartialStatePatchToAccount);
}

let Some(code) = patch.code().cloned() else {
return Err(AccountError::PartialStatePatchToAccount);
};
let Some(nonce) = patch.final_nonce() else {
return Err(AccountError::PartialStatePatchToAccount);
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: IDK how common returning one of these errors would be, but we could potentially have more granular errors, e.g. PartialStatePatchToAccountMissingNonce

Comment on lines +523 to +547
#[test]
fn account_try_from_partial_patch_fails() -> anyhow::Result<()> {
let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;

// Missing code.
let no_code = AccountPatch::new(
account_id,
AccountStoragePatch::new(),
AccountVaultPatch::default(),
None,
Some(Felt::from(2_u32)),
)?;
assert_matches!(
Account::try_from(&no_code).unwrap_err(),
AccountError::PartialStatePatchToAccount
);

// Missing final nonce (empty patch).
let empty = AccountPatch::new(
account_id,
AccountStoragePatch::new(),
AccountVaultPatch::default(),
None,
None,
)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could use rstest here to cover the two cases

@mmagician mmagician left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest LGTM!

@bobbinth bobbinth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you! I left just one comment inline.

Comment on lines +161 to +166
pub fn is_full_state(&self) -> bool {
// TODO(code_upgrades): Change this to another detection mechanism once we have code upgrade
// support, at which point the presence of code may not be enough of an indication that a
// patch can be converted to a full account.
self.code.is_some() && self.final_nonce.is_some()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - I see. In that case, wouldn't be just self.code.is_some() be sufficient? Basically, a combination of self.code.is_some() and self.final_nonce.is_none() shouldn't be possible by construction, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants