use crate::account::{Account, AccountId, AccountPatch};
use crate::errors::{
AccountPatchError,
AccountUpdateDetailsValidationError,
AccountUpdateSizeValidationError,
NewPublicAccountValidationError,
};
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{ACCOUNT_UPDATE_MAX_SIZE, Word};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AccountUpdateDetails {
Private,
Public(AccountPatch),
}
impl AccountUpdateDetails {
const PRIVATE_TAG: u8 = 0;
const PUBLIC_TAG: u8 = 1;
pub fn is_private(&self) -> bool {
matches!(self, Self::Private)
}
pub fn is_public(&self) -> bool {
matches!(self, Self::Public(_))
}
pub(crate) fn validate_size(
&self,
account_id: AccountId,
) -> Result<(), AccountUpdateSizeValidationError> {
let update_size = self.get_size_hint();
if update_size > ACCOUNT_UPDATE_MAX_SIZE as usize {
return Err(AccountUpdateSizeValidationError { account_id, update_size });
}
Ok(())
}
pub(crate) fn validate_for_account(
&self,
account_id: AccountId,
) -> Result<Option<&AccountPatch>, AccountUpdateDetailsValidationError> {
match (self, account_id.is_private()) {
(Self::Private, true) => Ok(None),
(Self::Public(_), true) => {
Err(AccountUpdateDetailsValidationError::PrivateAccountWithDetails(account_id))
},
(Self::Private, false) => Err(
AccountUpdateDetailsValidationError::PublicStateAccountMissingDetails(account_id),
),
(Self::Public(patch), false) if patch.id() != account_id => {
Err(AccountUpdateDetailsValidationError::AccountIdMismatch {
account_id,
patch_account_id: patch.id(),
})
},
(Self::Public(patch), false) => Ok(Some(patch)),
}
}
pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountPatchError> {
let merged_update = match (self, other) {
(AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
AccountUpdateDetails::Private
},
(AccountUpdateDetails::Public(mut patch), AccountUpdateDetails::Public(new_patch)) => {
patch.merge(new_patch)?;
AccountUpdateDetails::Public(patch)
},
(left, right) => {
return Err(AccountPatchError::IncompatibleAccountUpdates {
left_update_type: left.as_tag_str(),
right_update_type: right.as_tag_str(),
});
},
};
Ok(merged_update)
}
pub(crate) const fn as_tag_str(&self) -> &'static str {
match self {
AccountUpdateDetails::Private => "private",
AccountUpdateDetails::Public(_) => "public",
}
}
}
pub(crate) fn validate_new_public_account(
patch: &AccountPatch,
final_state_commitment: Word,
) -> Result<(), NewPublicAccountValidationError> {
let account = Account::try_from(patch).map_err(|source| {
NewPublicAccountValidationError::RequiresFullStatePatch { id: patch.id(), source }
})?;
let account_commitment = account.to_commitment();
if account_commitment != final_state_commitment {
return Err(NewPublicAccountValidationError::FinalCommitmentMismatch {
final_state_commitment,
account_commitment,
});
}
Ok(())
}
impl Serializable for AccountUpdateDetails {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
AccountUpdateDetails::Private => {
Self::PRIVATE_TAG.write_into(target);
},
AccountUpdateDetails::Public(public) => {
Self::PUBLIC_TAG.write_into(target);
public.write_into(target);
},
}
}
fn get_size_hint(&self) -> usize {
let u8_size = 0u8.get_size_hint();
match self {
AccountUpdateDetails::Private => u8_size,
AccountUpdateDetails::Public(public) => u8_size + public.get_size_hint(),
}
}
}
impl Deserializable for AccountUpdateDetails {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match u8::read_from(source)? {
Self::PRIVATE_TAG => Ok(Self::Private),
Self::PUBLIC_TAG => Ok(Self::Public(AccountPatch::read_from(source)?)),
variant => Err(DeserializationError::InvalidValue(format!(
"Unknown variant {variant} for AccountUpdateDetails"
))),
}
}
}
#[cfg(test)]
mod tests {
use super::AccountUpdateDetails;
use crate::account::{
AccountCode,
AccountId,
AccountPatch,
AccountStoragePatch,
AccountVaultPatch,
StorageMapKey,
StorageSlotName,
};
use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
use crate::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER;
use crate::utils::serde::Serializable;
use crate::{ONE, Word};
#[test]
fn account_update_details_size_hint() -> anyhow::Result<()> {
let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
let storage_patch = AccountStoragePatch::builder()
.create_value(StorageSlotName::mock(2), Word::from([1, 1, 1, 1u32]))
.create_value(StorageSlotName::mock(3), Word::from([1, 1, 0, 1u32]))
.create_map(
StorageSlotName::mock(4),
[(StorageMapKey::from_array([1, 1, 1, 1]), Word::from([1, 1, 1, 1u32]))],
)
.build();
let non_fungible: Asset = NonFungibleAsset::mock(&[6]);
let fungible: Asset = FungibleAsset::mock(42);
let vault_patch = AccountVaultPatch::with_assets([non_fungible, fungible]);
let account_patch = AccountPatch::new(
account_id,
storage_patch,
vault_patch,
Some(AccountCode::mock()),
Some(ONE),
)?;
let update_details_private = AccountUpdateDetails::Private;
assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
let update_details_patch = AccountUpdateDetails::Public(account_patch);
assert_eq!(update_details_patch.to_bytes().len(), update_details_patch.get_size_hint());
assert!(update_details_patch.get_size_hint() > update_details_private.get_size_hint());
Ok(())
}
}