use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use miden_protocol::Word;
use miden_protocol::account::{
AccountStorageDelta,
AccountStorageHeader,
PartialAccount,
StorageMapKey,
StorageSlotDelta,
StorageSlotHeader,
StorageSlotName,
StorageSlotType,
};
#[derive(Debug, Clone)]
pub struct StorageDeltaTracker {
is_account_new: bool,
storage_header: AccountStorageHeader,
init_maps: BTreeMap<StorageSlotName, BTreeMap<StorageMapKey, Word>>,
delta: AccountStorageDelta,
}
impl StorageDeltaTracker {
pub fn new(account: &PartialAccount) -> Self {
let initial_storage_header = if account.is_new() {
empty_storage_header_from_account(account)
} else {
account.storage().header().clone()
};
let mut storage_delta_tracker = Self {
is_account_new: account.is_new(),
storage_header: initial_storage_header,
init_maps: BTreeMap::new(),
delta: AccountStorageDelta::new(),
};
if account.is_new() {
account.storage().header().slots().for_each(|slot_header| {
match slot_header.slot_type() {
StorageSlotType::Value => {
storage_delta_tracker
.set_item(slot_header.name().clone(), slot_header.value());
},
StorageSlotType::Map => {
let storage_map = account
.storage()
.maps()
.find(|map| map.root() == slot_header.value())
.expect("storage map should be present in partial storage");
storage_delta_tracker
.delta
.insert_empty_map_delta(slot_header.name().clone());
storage_map.entries().for_each(|(key, value)| {
storage_delta_tracker.set_map_item(
slot_header.name().clone(),
*key,
Word::empty(),
*value,
);
});
},
}
});
}
storage_delta_tracker
}
pub fn set_item(&mut self, slot_name: StorageSlotName, new_value: Word) {
self.delta
.set_item(slot_name, new_value)
.expect("transaction kernel should not change slot types");
}
pub fn set_map_item(
&mut self,
slot_name: StorageSlotName,
key: StorageMapKey,
prev_value: Word,
new_value: Word,
) {
if prev_value != new_value {
self.set_init_map_item(slot_name.clone(), key, prev_value);
self.delta
.set_map_item(slot_name, key, new_value)
.expect("transaction kernel should not change slot types");
}
}
pub fn into_delta(self) -> AccountStorageDelta {
self.normalize()
}
fn set_init_map_item(
&mut self,
slot_name: StorageSlotName,
key: StorageMapKey,
prev_value: Word,
) {
let slot_map = self.init_maps.entry(slot_name).or_default();
slot_map.entry(key).or_insert(prev_value);
}
fn normalize(self) -> AccountStorageDelta {
let Self {
is_account_new,
storage_header,
init_maps,
delta,
} = self;
let mut deltas = delta.into_map();
deltas.retain(|slot_name, slot_delta| {
match slot_delta {
StorageSlotDelta::Value(new_value) => {
let slot_header = storage_header
.find_slot_header_by_name(slot_name)
.expect("slot name should exist");
is_account_new || *new_value != slot_header.value()
},
StorageSlotDelta::Map(map_delta) => {
let init_map = init_maps.get(slot_name);
if let Some(init_map) = init_map {
map_delta.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
});
}
is_account_new || !map_delta.is_empty()
},
}
});
AccountStorageDelta::from_raw(deltas)
}
}
fn empty_storage_header_from_account(account: &PartialAccount) -> AccountStorageHeader {
let slots: Vec<StorageSlotHeader> = account
.storage()
.header()
.slots()
.map(|slot_header| match slot_header.slot_type() {
StorageSlotType::Value => {
StorageSlotHeader::with_empty_value(slot_header.name().clone())
},
StorageSlotType::Map => StorageSlotHeader::with_empty_map(slot_header.name().clone()),
})
.collect();
AccountStorageHeader::new(slots).expect("storage header should be valid")
}