use alloc::string::ToString;
use super::{
Account, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable,
Word, ZERO,
};
use crate::AccountDeltaError;
mod storage;
pub use storage::{AccountStorageDelta, StorageMapDelta};
mod vault;
pub use vault::{
AccountVaultDelta, FungibleAssetDelta, NonFungibleAssetDelta, NonFungibleDeltaAction,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AccountDelta {
storage: AccountStorageDelta,
vault: AccountVaultDelta,
nonce: Option<Felt>,
}
impl AccountDelta {
pub fn new(
storage: AccountStorageDelta,
vault: AccountVaultDelta,
nonce: Option<Felt>,
) -> Result<Self, AccountDeltaError> {
validate_nonce(nonce, &storage, &vault)?;
Ok(Self { storage, vault, nonce })
}
pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
match (&mut self.nonce, other.nonce) {
(Some(old), Some(new)) if new.as_int() <= old.as_int() => {
return Err(AccountDeltaError::InconsistentNonceUpdate(format!(
"New nonce {new} is not larger than the old nonce {old}"
)))
},
(old, new) => *old = new.or(*old),
};
self.storage.merge(other.storage)?;
self.vault.merge(other.vault)
}
pub fn is_empty(&self) -> bool {
self.storage.is_empty() && self.vault.is_empty()
}
pub fn storage(&self) -> &AccountStorageDelta {
&self.storage
}
pub fn vault(&self) -> &AccountVaultDelta {
&self.vault
}
pub fn nonce(&self) -> Option<Felt> {
self.nonce
}
pub fn into_parts(self) -> (AccountStorageDelta, AccountVaultDelta, Option<Felt>) {
(self.storage, self.vault, self.nonce)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AccountUpdateDetails {
Private,
New(Account),
Delta(AccountDelta),
}
impl AccountUpdateDetails {
pub fn is_private(&self) -> bool {
matches!(self, Self::Private)
}
pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountDeltaError> {
let merged_update = match (self, other) {
(AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
AccountUpdateDetails::Private
},
(AccountUpdateDetails::New(mut account), AccountUpdateDetails::Delta(delta)) => {
account.apply_delta(&delta).map_err(|_| {
AccountDeltaError::IncompatibleAccountUpdates(
AccountUpdateDetails::New(account.clone()),
AccountUpdateDetails::Delta(delta),
)
})?;
AccountUpdateDetails::New(account)
},
(AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => {
delta.merge(new_delta)?;
AccountUpdateDetails::Delta(delta)
},
(left, right) => {
return Err(AccountDeltaError::IncompatibleAccountUpdates(left, right))
},
};
Ok(merged_update)
}
}
impl Serializable for AccountDelta {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.storage.write_into(target);
self.vault.write_into(target);
self.nonce.write_into(target);
}
fn get_size_hint(&self) -> usize {
self.storage.get_size_hint() + self.vault.get_size_hint() + self.nonce.get_size_hint()
}
}
impl Deserializable for AccountDelta {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let storage = AccountStorageDelta::read_from(source)?;
let vault = AccountVaultDelta::read_from(source)?;
let nonce = <Option<Felt>>::read_from(source)?;
validate_nonce(nonce, &storage, &vault)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
Ok(Self { storage, vault, nonce })
}
}
impl Serializable for AccountUpdateDetails {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
AccountUpdateDetails::Private => {
0_u8.write_into(target);
},
AccountUpdateDetails::New(account) => {
1_u8.write_into(target);
account.write_into(target);
},
AccountUpdateDetails::Delta(delta) => {
2_u8.write_into(target);
delta.write_into(target);
},
}
}
fn get_size_hint(&self) -> usize {
let u8_size = 0u8.get_size_hint();
match self {
AccountUpdateDetails::Private => u8_size,
AccountUpdateDetails::New(account) => u8_size + account.get_size_hint(),
AccountUpdateDetails::Delta(account_delta) => u8_size + account_delta.get_size_hint(),
}
}
}
impl Deserializable for AccountUpdateDetails {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match u8::read_from(source)? {
0 => Ok(Self::Private),
1 => Ok(Self::New(Account::read_from(source)?)),
2 => Ok(Self::Delta(AccountDelta::read_from(source)?)),
v => Err(DeserializationError::InvalidValue(format!(
"Unknown variant {v} for AccountDetails"
))),
}
}
}
fn validate_nonce(
nonce: Option<Felt>,
storage: &AccountStorageDelta,
vault: &AccountVaultDelta,
) -> Result<(), AccountDeltaError> {
if !storage.is_empty() || !vault.is_empty() {
match nonce {
Some(nonce) => {
if nonce == ZERO {
return Err(AccountDeltaError::InconsistentNonceUpdate(
"zero nonce for a non-empty account delta".to_string(),
));
}
},
None => {
return Err(AccountDeltaError::InconsistentNonceUpdate(
"nonce not updated for non-empty account delta".to_string(),
))
},
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use vm_core::{utils::Serializable, Felt, FieldElement};
use super::{AccountDelta, AccountStorageDelta, AccountVaultDelta};
use crate::{
accounts::{
account_id::testing::ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
delta::AccountUpdateDetails, Account, AccountCode, AccountId, AccountStorage,
AccountType, StorageMapDelta,
},
assets::{Asset, AssetVault, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails},
ONE, ZERO,
};
#[test]
fn account_delta_nonce_validation() {
let storage_delta = AccountStorageDelta::default();
let vault_delta = AccountVaultDelta::default();
assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_ok());
assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok());
let storage_delta = AccountStorageDelta::from_iters([1], [], []);
assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_err());
assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ZERO)).is_err());
assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok());
}
#[test]
fn account_update_details_size_hint() {
let storage_delta = AccountStorageDelta::default();
let vault_delta = AccountVaultDelta::default();
assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
let account_delta = AccountDelta::new(storage_delta, vault_delta, None).unwrap();
assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
let storage_delta = AccountStorageDelta::from_iters(
[1],
[(2, [ONE, ONE, ONE, ONE]), (3, [ONE, ONE, ZERO, ONE])],
[(
4,
StorageMapDelta::from_iters(
[[ONE, ONE, ONE, ZERO], [ZERO, ONE, ONE, ONE]],
[([ONE, ONE, ONE, ONE], [ONE, ONE, ONE, ONE])],
),
)],
);
let non_fungible: Asset = NonFungibleAsset::new(
&NonFungibleAssetDetails::new(
AccountId::new_dummy([10; 32], AccountType::NonFungibleFaucet),
vec![6],
)
.unwrap(),
)
.unwrap()
.into();
let fungible_2: Asset =
FungibleAsset::new(AccountId::new_dummy([10; 32], AccountType::FungibleFaucet), 10)
.unwrap()
.into();
let vault_delta = AccountVaultDelta::from_iters([non_fungible], [fungible_2]);
assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
let account_delta = AccountDelta::new(storage_delta, vault_delta, Some(ONE)).unwrap();
assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
let account_id =
AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap();
let asset_vault = AssetVault::mock();
assert_eq!(asset_vault.to_bytes().len(), asset_vault.get_size_hint());
let account_storage = AccountStorage::mock();
assert_eq!(account_storage.to_bytes().len(), account_storage.get_size_hint());
let account_code = AccountCode::mock();
assert_eq!(account_code.to_bytes().len(), account_code.get_size_hint());
let account =
Account::from_parts(account_id, asset_vault, account_storage, account_code, Felt::ZERO);
assert_eq!(account.to_bytes().len(), account.get_size_hint());
let update_details_private = AccountUpdateDetails::Private;
assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
let update_details_delta = AccountUpdateDetails::Delta(account_delta);
assert_eq!(update_details_delta.to_bytes().len(), update_details_delta.get_size_hint());
let update_details_new = AccountUpdateDetails::New(account);
assert_eq!(update_details_new.to_bytes().len(), update_details_new.get_size_hint());
}
}