use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use miden_core::{Felt, Word};
use crate::account::{
AccountStorage,
AccountStoragePatch,
StorageMap,
StorageMapKey,
StorageMapPatch,
StorageMapPatchEntries,
StorageSlot,
StorageSlotName,
StorageSlotPatch,
StorageValuePatch,
};
use crate::utils::sync::LazyLock;
impl AccountStoragePatch {
pub fn from_iters(
cleared_values: impl IntoIterator<Item = StorageSlotName>,
updated_values: impl IntoIterator<Item = (StorageSlotName, Word)>,
updated_maps: impl IntoIterator<Item = (StorageSlotName, StorageMapPatch)>,
) -> Self {
let patches: BTreeMap<_, _> = cleared_values
.into_iter()
.map(|slot_name| {
(
slot_name,
StorageSlotPatch::Value(StorageValuePatch::Update { value: Word::empty() }),
)
})
.chain(updated_values.into_iter().map(|(slot_name, value)| {
(slot_name, StorageSlotPatch::Value(StorageValuePatch::Update { value }))
}))
.chain(
updated_maps
.into_iter()
.map(|(slot_name, map_patch)| (slot_name, StorageSlotPatch::Map(map_patch))),
)
.collect();
Self::from_raw(patches).expect("number of slot patches should be within limits")
}
pub fn builder() -> AccountStoragePatchBuilder {
AccountStoragePatchBuilder::new()
}
pub fn created_value(&self, slot_name: &StorageSlotName) -> Option<Word> {
match self.get(slot_name) {
Some(StorageSlotPatch::Value(StorageValuePatch::Create { value })) => Some(*value),
_ => None,
}
}
pub fn updated_value(&self, slot_name: &StorageSlotName) -> Option<Word> {
match self.get(slot_name) {
Some(StorageSlotPatch::Value(StorageValuePatch::Update { value })) => Some(*value),
_ => None,
}
}
pub fn is_value_removed(&self, slot_name: &StorageSlotName) -> bool {
matches!(self.get(slot_name), Some(StorageSlotPatch::Value(StorageValuePatch::Remove)))
}
pub fn created_map(&self, slot_name: &StorageSlotName) -> Option<&StorageMapPatchEntries> {
match self.get(slot_name) {
Some(StorageSlotPatch::Map(StorageMapPatch::Create { entries })) => Some(entries),
_ => None,
}
}
pub fn updated_map(&self, slot_name: &StorageSlotName) -> Option<&StorageMapPatchEntries> {
match self.get(slot_name) {
Some(StorageSlotPatch::Map(StorageMapPatch::Update { entries })) => Some(entries),
_ => None,
}
}
pub fn is_map_removed(&self, slot_name: &StorageSlotName) -> bool {
matches!(self.get(slot_name), Some(StorageSlotPatch::Map(StorageMapPatch::Remove)))
}
pub fn created_map_item(
&self,
slot_name: &StorageSlotName,
key: &StorageMapKey,
) -> Option<Word> {
self.created_map(slot_name)?.as_map().get(key).copied()
}
pub fn updated_map_item(
&self,
slot_name: &StorageSlotName,
key: &StorageMapKey,
) -> Option<Word> {
self.updated_map(slot_name)?.as_map().get(key).copied()
}
}
#[derive(Clone, Debug, Default)]
pub struct AccountStoragePatchBuilder {
patches: BTreeMap<StorageSlotName, StorageSlotPatch>,
}
impl AccountStoragePatchBuilder {
pub fn new() -> Self {
Self { patches: BTreeMap::new() }
}
pub fn create_value(self, slot_name: StorageSlotName, value: Word) -> Self {
self.set_unique_slot(
slot_name,
StorageSlotPatch::Value(StorageValuePatch::Create { value }),
)
}
pub fn update_value(self, slot_name: StorageSlotName, value: Word) -> Self {
self.set_unique_slot(
slot_name,
StorageSlotPatch::Value(StorageValuePatch::Update { value }),
)
}
pub fn remove_value(self, slot_name: StorageSlotName) -> Self {
self.set_unique_slot(slot_name, StorageSlotPatch::Value(StorageValuePatch::Remove))
}
pub fn create_map(
self,
slot_name: StorageSlotName,
entries: impl IntoIterator<Item = (StorageMapKey, Word)>,
) -> Self {
self.insert_map_entries(slot_name, true, entries)
}
pub fn update_map(
self,
slot_name: StorageSlotName,
entries: impl IntoIterator<Item = (StorageMapKey, Word)>,
) -> Self {
self.insert_map_entries(slot_name, false, entries)
}
pub fn remove_map(self, slot_name: StorageSlotName) -> Self {
self.set_unique_slot(slot_name, StorageSlotPatch::Map(StorageMapPatch::Remove))
}
pub fn build(self) -> AccountStoragePatch {
AccountStoragePatch::from_raw(self.patches)
.expect("number of slot patches should be within limits")
}
fn set_unique_slot(mut self, slot_name: StorageSlotName, patch: StorageSlotPatch) -> Self {
if self.patches.insert(slot_name.clone(), patch).is_some() {
panic!("storage slot `{slot_name}` was already set");
}
self
}
fn insert_map_entries(
mut self,
slot_name: StorageSlotName,
create: bool,
entries: impl IntoIterator<Item = (StorageMapKey, Word)>,
) -> Self {
let slot_patch = self.patches.entry(slot_name.clone()).or_insert_with(|| {
let entries = StorageMapPatchEntries::new();
let map_patch = if create {
StorageMapPatch::Create { entries }
} else {
StorageMapPatch::Update { entries }
};
StorageSlotPatch::Map(map_patch)
});
let existing_entries = match slot_patch {
StorageSlotPatch::Map(StorageMapPatch::Create { entries }) if create => entries,
StorageSlotPatch::Map(StorageMapPatch::Update { entries }) if !create => entries,
_ => panic!(
"storage slot `{slot_name}` was already set as a different slot or map operation type"
),
};
for (key, value) in entries {
if existing_entries
.as_map()
.get(&key)
.is_some_and(|current| *current != Word::empty())
{
panic!("map key `{key:?}` in storage slot `{slot_name}` was already set");
}
existing_entries.insert(key, value);
}
self
}
}
impl StorageMapPatch {
pub fn from_iters(
cleared_keys: impl IntoIterator<Item = StorageMapKey>,
updated_entries: impl IntoIterator<Item = (StorageMapKey, Word)>,
) -> Self {
StorageMapPatch::Update {
entries: StorageMapPatchEntries::from_iters(cleared_keys, updated_entries),
}
}
}
impl StorageMapPatchEntries {
pub fn from_iters(
cleared_keys: impl IntoIterator<Item = StorageMapKey>,
updated_entries: impl IntoIterator<Item = (StorageMapKey, Word)>,
) -> Self {
Self::from_raw(BTreeMap::from_iter(
cleared_keys.into_iter().map(|key| (key, Word::empty())).chain(updated_entries),
))
}
}
pub static MOCK_VALUE_SLOT0: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::test::value0").expect("storage slot name should be valid")
});
pub static MOCK_VALUE_SLOT1: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::test::value1").expect("storage slot name should be valid")
});
pub static MOCK_MAP_SLOT: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::test::map").expect("storage slot name should be valid")
});
pub const STORAGE_VALUE_0: Word = Word::new([
Felt::ONE,
Felt::new_unchecked(2),
Felt::new_unchecked(3),
Felt::new_unchecked(4),
]);
pub const STORAGE_VALUE_1: Word = Word::new([
Felt::new_unchecked(5),
Felt::new_unchecked(6),
Felt::new_unchecked(7),
Felt::new_unchecked(8),
]);
pub const STORAGE_LEAVES_2: [(Word, Word); 2] = [
(
Word::new([
Felt::new_unchecked(101),
Felt::new_unchecked(102),
Felt::new_unchecked(103),
Felt::new_unchecked(104),
]),
Word::new([
Felt::new_unchecked(1),
Felt::new_unchecked(2),
Felt::new_unchecked(3),
Felt::new_unchecked(4),
]),
),
(
Word::new([
Felt::new_unchecked(105),
Felt::new_unchecked(106),
Felt::new_unchecked(107),
Felt::new_unchecked(108),
]),
Word::new([
Felt::new_unchecked(5),
Felt::new_unchecked(6),
Felt::new_unchecked(7),
Felt::new_unchecked(8),
]),
),
];
impl AccountStorage {
pub fn mock() -> Self {
AccountStorage::new(Self::mock_storage_slots()).unwrap()
}
pub fn mock_storage_slots() -> Vec<StorageSlot> {
vec![Self::mock_value_slot0(), Self::mock_value_slot1(), Self::mock_map_slot()]
}
pub fn mock_value_slot0() -> StorageSlot {
StorageSlot::with_value(MOCK_VALUE_SLOT0.clone(), STORAGE_VALUE_0)
}
pub fn mock_value_slot1() -> StorageSlot {
StorageSlot::with_value(MOCK_VALUE_SLOT1.clone(), STORAGE_VALUE_1)
}
pub fn mock_map_slot() -> StorageSlot {
StorageSlot::with_map(MOCK_MAP_SLOT.clone(), Self::mock_map())
}
pub fn mock_map() -> StorageMap {
StorageMap::with_entries(
STORAGE_LEAVES_2.map(|(key, value)| (StorageMapKey::from_raw(key), value)),
)
.unwrap()
}
}