1use alloc::string::ToString;
2
3use super::{
4 Account, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable,
5 Word, ZERO,
6};
7use crate::AccountDeltaError;
8
9mod storage;
10pub use storage::{AccountStorageDelta, StorageMapDelta};
11
12mod vault;
13pub use vault::{
14 AccountVaultDelta, FungibleAssetDelta, NonFungibleAssetDelta, NonFungibleDeltaAction,
15};
16
17#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct AccountDelta {
30 storage: AccountStorageDelta,
31 vault: AccountVaultDelta,
32 nonce: Option<Felt>,
33}
34
35impl AccountDelta {
36 pub fn new(
45 storage: AccountStorageDelta,
46 vault: AccountVaultDelta,
47 nonce: Option<Felt>,
48 ) -> Result<Self, AccountDeltaError> {
49 validate_nonce(nonce, &storage, &vault)?;
51
52 Ok(Self { storage, vault, nonce })
53 }
54
55 pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
57 match (&mut self.nonce, other.nonce) {
58 (Some(old), Some(new)) if new.as_int() <= old.as_int() => {
59 return Err(AccountDeltaError::InconsistentNonceUpdate(format!(
60 "new nonce {new} is not larger than the old nonce {old}"
61 )))
62 },
63 (old, new) => *old = new.or(*old),
65 };
66 self.storage.merge(other.storage)?;
67 self.vault.merge(other.vault)
68 }
69
70 pub fn is_empty(&self) -> bool {
75 self.storage.is_empty() && self.vault.is_empty()
76 }
77
78 pub fn storage(&self) -> &AccountStorageDelta {
80 &self.storage
81 }
82
83 pub fn vault(&self) -> &AccountVaultDelta {
85 &self.vault
86 }
87
88 pub fn nonce(&self) -> Option<Felt> {
90 self.nonce
91 }
92
93 pub fn into_parts(self) -> (AccountStorageDelta, AccountVaultDelta, Option<Felt>) {
95 (self.storage, self.vault, self.nonce)
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum AccountUpdateDetails {
103 Private,
105
106 New(Account),
108
109 Delta(AccountDelta),
111}
112
113impl AccountUpdateDetails {
114 pub fn is_private(&self) -> bool {
116 matches!(self, Self::Private)
117 }
118
119 pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountDeltaError> {
123 let merged_update = match (self, other) {
124 (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
125 AccountUpdateDetails::Private
126 },
127 (AccountUpdateDetails::New(mut account), AccountUpdateDetails::Delta(delta)) => {
128 account.apply_delta(&delta).map_err(|err| {
129 AccountDeltaError::AccountDeltaApplicationFailed {
130 account_id: account.id(),
131 source: err,
132 }
133 })?;
134
135 AccountUpdateDetails::New(account)
136 },
137 (AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => {
138 delta.merge(new_delta)?;
139 AccountUpdateDetails::Delta(delta)
140 },
141 (left, right) => {
142 return Err(AccountDeltaError::IncompatibleAccountUpdates {
143 left_update_type: left.as_tag_str(),
144 right_update_type: right.as_tag_str(),
145 })
146 },
147 };
148
149 Ok(merged_update)
150 }
151
152 pub(crate) const fn as_tag_str(&self) -> &'static str {
154 match self {
155 AccountUpdateDetails::Private => "private",
156 AccountUpdateDetails::New(_) => "new",
157 AccountUpdateDetails::Delta(_) => "delta",
158 }
159 }
160}
161
162impl From<Account> for AccountDelta {
164 fn from(account: Account) -> Self {
165 let (_id, vault, storage, _code, nonce) = account.into_parts();
166 AccountDelta {
167 storage: storage.into(),
168 vault: (&vault).into(),
169 nonce: Some(nonce),
170 }
171 }
172}
173
174impl Serializable for AccountDelta {
178 fn write_into<W: ByteWriter>(&self, target: &mut W) {
179 self.storage.write_into(target);
180 self.vault.write_into(target);
181 self.nonce.write_into(target);
182 }
183
184 fn get_size_hint(&self) -> usize {
185 self.storage.get_size_hint() + self.vault.get_size_hint() + self.nonce.get_size_hint()
186 }
187}
188
189impl Deserializable for AccountDelta {
190 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
191 let storage = AccountStorageDelta::read_from(source)?;
192 let vault = AccountVaultDelta::read_from(source)?;
193 let nonce = <Option<Felt>>::read_from(source)?;
194
195 validate_nonce(nonce, &storage, &vault)
196 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
197
198 Ok(Self { storage, vault, nonce })
199 }
200}
201
202impl Serializable for AccountUpdateDetails {
203 fn write_into<W: ByteWriter>(&self, target: &mut W) {
204 match self {
205 AccountUpdateDetails::Private => {
206 0_u8.write_into(target);
207 },
208 AccountUpdateDetails::New(account) => {
209 1_u8.write_into(target);
210 account.write_into(target);
211 },
212 AccountUpdateDetails::Delta(delta) => {
213 2_u8.write_into(target);
214 delta.write_into(target);
215 },
216 }
217 }
218
219 fn get_size_hint(&self) -> usize {
220 let u8_size = 0u8.get_size_hint();
222
223 match self {
224 AccountUpdateDetails::Private => u8_size,
225 AccountUpdateDetails::New(account) => u8_size + account.get_size_hint(),
226 AccountUpdateDetails::Delta(account_delta) => u8_size + account_delta.get_size_hint(),
227 }
228 }
229}
230
231impl Deserializable for AccountUpdateDetails {
232 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
233 match u8::read_from(source)? {
234 0 => Ok(Self::Private),
235 1 => Ok(Self::New(Account::read_from(source)?)),
236 2 => Ok(Self::Delta(AccountDelta::read_from(source)?)),
237 v => Err(DeserializationError::InvalidValue(format!(
238 "Unknown variant {v} for AccountDetails"
239 ))),
240 }
241 }
242}
243
244fn validate_nonce(
253 nonce: Option<Felt>,
254 storage: &AccountStorageDelta,
255 vault: &AccountVaultDelta,
256) -> Result<(), AccountDeltaError> {
257 if !storage.is_empty() || !vault.is_empty() {
258 match nonce {
259 Some(nonce) => {
260 if nonce == ZERO {
261 return Err(AccountDeltaError::InconsistentNonceUpdate(
262 "zero nonce for a non-empty account delta".to_string(),
263 ));
264 }
265 },
266 None => {
267 return Err(AccountDeltaError::InconsistentNonceUpdate(
268 "nonce not updated for non-empty account delta".to_string(),
269 ))
270 },
271 }
272 }
273
274 Ok(())
275}
276
277#[cfg(test)]
281mod tests {
282
283 use vm_core::{utils::Serializable, Felt, FieldElement};
284
285 use super::{AccountDelta, AccountStorageDelta, AccountVaultDelta};
286 use crate::{
287 account::{
288 delta::AccountUpdateDetails, Account, AccountCode, AccountId, AccountStorage,
289 AccountStorageMode, AccountType, StorageMapDelta,
290 },
291 asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails},
292 testing::account_id::{
293 AccountIdBuilder, ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
294 },
295 ONE, ZERO,
296 };
297
298 #[test]
299 fn account_delta_nonce_validation() {
300 let storage_delta = AccountStorageDelta::default();
302 let vault_delta = AccountVaultDelta::default();
303
304 assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_ok());
305 assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok());
306
307 let storage_delta = AccountStorageDelta::from_iters([1], [], []);
309
310 assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_err());
311 assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ZERO)).is_err());
312 assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok());
313 }
314
315 #[test]
316 fn account_update_details_size_hint() {
317 let storage_delta = AccountStorageDelta::default();
320 let vault_delta = AccountVaultDelta::default();
321 assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
322 assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
323
324 let account_delta = AccountDelta::new(storage_delta, vault_delta, None).unwrap();
325 assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
326
327 let storage_delta = AccountStorageDelta::from_iters(
328 [1],
329 [(2, [ONE, ONE, ONE, ONE]), (3, [ONE, ONE, ZERO, ONE])],
330 [(
331 4,
332 StorageMapDelta::from_iters(
333 [[ONE, ONE, ONE, ZERO], [ZERO, ONE, ONE, ONE]],
334 [([ONE, ONE, ONE, ONE], [ONE, ONE, ONE, ONE])],
335 ),
336 )],
337 );
338
339 let non_fungible: Asset = NonFungibleAsset::new(
340 &NonFungibleAssetDetails::new(
341 AccountIdBuilder::new()
342 .account_type(AccountType::NonFungibleFaucet)
343 .storage_mode(AccountStorageMode::Public)
344 .build_with_rng(&mut rand::thread_rng())
345 .prefix(),
346 vec![6],
347 )
348 .unwrap(),
349 )
350 .unwrap()
351 .into();
352 let fungible_2: Asset = FungibleAsset::new(
353 AccountIdBuilder::new()
354 .account_type(AccountType::FungibleFaucet)
355 .storage_mode(AccountStorageMode::Public)
356 .build_with_rng(&mut rand::thread_rng()),
357 10,
358 )
359 .unwrap()
360 .into();
361 let vault_delta = AccountVaultDelta::from_iters([non_fungible], [fungible_2]);
362
363 assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
364 assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
365
366 let account_delta = AccountDelta::new(storage_delta, vault_delta, Some(ONE)).unwrap();
367 assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
368
369 let account_id =
372 AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap();
373
374 let asset_vault = AssetVault::mock();
375 assert_eq!(asset_vault.to_bytes().len(), asset_vault.get_size_hint());
376
377 let account_storage = AccountStorage::mock();
378 assert_eq!(account_storage.to_bytes().len(), account_storage.get_size_hint());
379
380 let account_code = AccountCode::mock();
381 assert_eq!(account_code.to_bytes().len(), account_code.get_size_hint());
382
383 let account =
384 Account::from_parts(account_id, asset_vault, account_storage, account_code, Felt::ZERO);
385 assert_eq!(account.to_bytes().len(), account.get_size_hint());
386
387 let update_details_private = AccountUpdateDetails::Private;
390 assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
391
392 let update_details_delta = AccountUpdateDetails::Delta(account_delta);
393 assert_eq!(update_details_delta.to_bytes().len(), update_details_delta.get_size_hint());
394
395 let update_details_new = AccountUpdateDetails::New(account);
396 assert_eq!(update_details_new.to_bytes().len(), update_details_new.get_size_hint());
397 }
398}