feat: implement serialization for AccountPatch and AccountVaultPatch and apply_patch#3071
feat: implement serialization for AccountPatch and AccountVaultPatch and apply_patch#3071PhilippGackstatter wants to merge 41 commits into
AccountPatch and AccountVaultPatch and apply_patch#3071Conversation
…o pgackst-unified-asset-delta
9d69cc7 to
0a0228d
Compare
AccountPatch and AccountVaultPatchAccountPatch and AccountVaultPatch and apply_patch
fa79ae2 to
1c6c264
Compare
1c6c264 to
985197b
Compare
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I left just a few comments inline - the main one is about serialization.
| 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() | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
could we potentially disallow merging patches with final_nonce == 1?
There was a problem hiding this comment.
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?
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>
|
Pushed a small additional commit to implement |
mmagician
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
| // 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 |
| 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() | ||
| } |
There was a problem hiding this comment.
could we potentially disallow merging patches with final_nonce == 1?
| 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); | ||
| }; |
There was a problem hiding this comment.
nit: IDK how common returning one of these errors would be, but we could potentially have more granular errors, e.g. PartialStatePatchToAccountMissingNonce
| #[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, | ||
| )?; |
There was a problem hiding this comment.
nit: could use rstest here to cover the two cases
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I left just one comment inline.
| 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() | ||
| } |
There was a problem hiding this comment.
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?
Implements:
AccountPatchandAccountVaultPatch. The latter's serialization for fungible assets is optimized to save space, as suggested by @bobbinth.Account::apply_patchAPI.impl TryFrom<&AccountPatch> for Accountapply_delta(ensuring the delta is applied to the right account).Needed for including
AccountPatchinExecutedTransactionand replacingAccountDelta, so part of #2630.