use alloc::collections::BTreeMap;
use miden_protocol::Word;
use miden_protocol::account::{
AccountStorageHeader,
AccountStoragePatch,
PartialAccount,
StorageMapKey,
StorageMapPatch,
StorageMapPatchEntries,
StorageSlotName,
StorageSlotPatch,
StorageSlotType,
StorageValuePatch,
};
use crate::TransactionKernelError;
#[derive(Debug, Clone)]
pub struct StoragePatchTracker {
storage_header: AccountStorageHeader,
init_maps: BTreeMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>,
patch: AccountStoragePatch,
}
impl StoragePatchTracker {
pub fn new(account: &PartialAccount) -> Self {
let mut init_maps = BTreeMap::new();
let mut patches = BTreeMap::new();
if account.is_new() {
account.storage().header().slots().for_each(|slot_header| {
match slot_header.slot_type() {
StorageSlotType::Value => {
let prev_entry = patches.insert(
slot_header.name().clone(),
StorageSlotPatch::Value(StorageValuePatch::Create {
value: slot_header.value(),
}),
);
assert!(prev_entry.is_none(), "storage header should contain unique slots");
},
StorageSlotType::Map => {
let storage_map = account
.storage()
.maps()
.find(|map| map.root() == slot_header.value())
.expect("storage map should be present in partial storage");
let mut map_patch_entries = StorageMapPatchEntries::new();
storage_map.entries().for_each(|(key, value)| {
set_init_map_item(
&mut init_maps,
slot_header.name().clone(),
*key,
Word::empty(),
);
map_patch_entries.insert(*key, *value);
});
let prev_entry = patches.insert(
slot_header.name().clone(),
StorageSlotPatch::Map(StorageMapPatch::Create {
entries: map_patch_entries,
}),
);
assert!(prev_entry.is_none(), "storage header should contain unique slots");
},
}
});
}
Self {
storage_header: account.storage().header().clone(),
init_maps,
patch: AccountStoragePatch::from_raw(patches)
.expect("number of slot patches is bounded by the account's storage slots"),
}
}
pub fn set_item(
&mut self,
slot_name: StorageSlotName,
new_value: Word,
) -> Result<(), TransactionKernelError> {
let update_patch = AccountStoragePatch::from_raw(BTreeMap::from_iter([(
slot_name,
StorageSlotPatch::Value(StorageValuePatch::Update { value: new_value }),
)]))
.expect("single entry does not exceed max num entries");
self.patch.merge(update_patch).map_err(|source| {
TransactionKernelError::other_with_source("failed to set_item on patch", source)
})?;
Ok(())
}
pub fn set_map_item(
&mut self,
slot_name: StorageSlotName,
key: StorageMapKey,
prev_value: Word,
new_value: Word,
) -> Result<(), TransactionKernelError> {
if prev_value != new_value {
set_init_map_item(&mut self.init_maps, slot_name.clone(), key, prev_value);
let update_patch = AccountStoragePatch::from_raw(BTreeMap::from_iter([(
slot_name,
StorageSlotPatch::Map(StorageMapPatch::Update {
entries: StorageMapPatchEntries::from_iter([(key, new_value)]),
}),
)]))
.expect("single entry does not exceed max num entries");
self.patch.merge(update_patch).map_err(|source| {
TransactionKernelError::other_with_source("failed to set_map_item on patch", source)
})?;
}
Ok(())
}
pub fn into_patch(self) -> AccountStoragePatch {
self.normalize()
}
fn normalize(self) -> AccountStoragePatch {
let Self { storage_header, init_maps, patch, .. } = self;
let mut patches = patch.into_map();
patches.retain(|slot_name, slot_patch| match slot_patch {
StorageSlotPatch::Value(value_patch) => match value_patch {
StorageValuePatch::Create { .. } | StorageValuePatch::Remove => true,
StorageValuePatch::Update { value } => {
let slot_header = storage_header
.find_slot_header_by_name(slot_name)
.expect("slot name should exist");
*value != slot_header.value()
},
},
StorageSlotPatch::Map(map_patch) => match map_patch {
StorageMapPatch::Create { entries } => {
entries.as_map_mut().retain(|_key, value| {
!value.is_empty()
});
true
},
StorageMapPatch::Remove => true,
StorageMapPatch::Update { entries } => {
if let Some(init_map) = init_maps.get(slot_name) {
entries.as_map_mut().retain(|key, new_value| {
let initial_value = init_map.get(key).expect(
"the initial value should be present for every value that was updated",
);
new_value != initial_value
});
}
!entries.is_empty()
},
},
});
AccountStoragePatch::from_raw(patches)
.expect("normalization does not increase the number of slot patches")
}
}
fn set_init_map_item(
init_maps: &mut BTreeMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>,
slot_name: StorageSlotName,
key: StorageMapKey,
prev_value: Word,
) {
let slot_map = init_maps.entry(slot_name).or_default();
slot_map.entry(key).or_insert(prev_value);
}