use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use thiserror::Error;
use super::StorageValueName;
use super::value_name::StorageValueNameError;
use crate::account::StorageSlotName;
use crate::errors::StorageSlotNameError;
use crate::{Felt, Word};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WordValue {
FullyTyped(Word),
Atomic(String),
Elements([String; 4]),
}
impl From<Word> for WordValue {
fn from(value: Word) -> Self {
WordValue::FullyTyped(value)
}
}
impl From<String> for WordValue {
fn from(value: String) -> Self {
WordValue::Atomic(value)
}
}
impl From<&str> for WordValue {
fn from(value: &str) -> Self {
WordValue::Atomic(String::from(value))
}
}
impl From<Felt> for WordValue {
fn from(value: Felt) -> Self {
WordValue::FullyTyped(Word::from([value, Felt::ZERO, Felt::ZERO, Felt::ZERO]))
}
}
impl From<[Felt; 4]> for WordValue {
fn from(value: [Felt; 4]) -> Self {
WordValue::FullyTyped(Word::from(value))
}
}
impl From<u8> for WordValue {
fn from(value: u8) -> Self {
WordValue::Atomic(value.to_string())
}
}
impl From<u16> for WordValue {
fn from(value: u16) -> Self {
WordValue::Atomic(value.to_string())
}
}
impl From<u32> for WordValue {
fn from(value: u32) -> Self {
WordValue::Atomic(value.to_string())
}
}
impl From<u64> for WordValue {
fn from(value: u64) -> Self {
WordValue::Atomic(value.to_string())
}
}
impl From<[u32; 4]> for WordValue {
fn from(value: [u32; 4]) -> Self {
WordValue::FullyTyped(Word::from([
Felt::from(value[0]),
Felt::from(value[1]),
Felt::from(value[2]),
Felt::from(value[3]),
]))
}
}
#[derive(Clone, Debug, Default)]
pub struct InitStorageData {
value_entries: BTreeMap<StorageValueName, WordValue>,
map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>,
}
impl InitStorageData {
pub fn new(
value_entries: BTreeMap<StorageValueName, WordValue>,
map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>,
) -> Result<Self, InitStorageDataError> {
for slot_name in map_entries.keys() {
if value_entries.keys().any(|v| v.slot_name() == slot_name) {
return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
}
}
for value_name in value_entries.keys() {
if value_name.field_name().is_none() {
let has_field_entries = value_entries.keys().any(|other| {
other.slot_name() == value_name.slot_name() && other.field_name().is_some()
});
if has_field_entries {
return Err(InitStorageDataError::ConflictingEntries(
value_name.slot_name().as_str().into(),
));
}
}
}
Ok(InitStorageData { value_entries, map_entries })
}
pub fn values(&self) -> &BTreeMap<StorageValueName, WordValue> {
&self.value_entries
}
pub fn maps(&self) -> &BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>> {
&self.map_entries
}
pub fn value_entry(&self, name: &StorageValueName) -> Option<&WordValue> {
self.value_entries.get(name)
}
pub fn slot_value_entry(&self, slot_name: &StorageSlotName) -> Option<&WordValue> {
let name = StorageValueName::from_slot_name(slot_name);
self.value_entries.get(&name)
}
pub fn map_entries(&self, slot_name: &StorageSlotName) -> Option<&Vec<(WordValue, WordValue)>> {
self.map_entries.get(slot_name)
}
pub fn has_value_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool {
self.value_entries.keys().any(|name| name.slot_name() == slot_name)
}
pub fn has_field_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool {
self.value_entries
.keys()
.any(|name| name.slot_name() == slot_name && name.field_name().is_some())
}
pub fn insert_value<N, E>(
&mut self,
name: N,
value: impl Into<WordValue>,
) -> Result<(), InitStorageDataError>
where
N: TryInto<StorageValueName, Error = E>,
InitStorageDataError: From<E>,
{
let name = name.try_into().map_err(InitStorageDataError::from)?;
if self.value_entries.contains_key(&name) {
return Err(InitStorageDataError::DuplicateKey(name.to_string()));
}
if self.map_entries.contains_key(name.slot_name()) {
return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into()));
}
self.value_entries.insert(name, value.into());
Ok(())
}
pub fn set_value<N, E>(
&mut self,
name: N,
value: impl Into<WordValue>,
) -> Result<(), InitStorageDataError>
where
N: TryInto<StorageValueName, Error = E>,
InitStorageDataError: From<E>,
{
let name = name.try_into().map_err(InitStorageDataError::from)?;
if self.map_entries.contains_key(name.slot_name()) {
return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into()));
}
self.value_entries.insert(name, value.into());
Ok(())
}
pub fn insert_map_entry<S, E>(
&mut self,
slot_name: S,
key: impl Into<WordValue>,
value: impl Into<WordValue>,
) -> Result<(), InitStorageDataError>
where
S: TryInto<StorageSlotName, Error = E>,
InitStorageDataError: From<E>,
{
let slot_name = slot_name.try_into().map_err(InitStorageDataError::from)?;
if self.has_value_entries_for_slot(&slot_name) {
return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
}
let key = key.into();
if let Some(entries) = self.map_entries.get(&slot_name)
&& entries.iter().any(|(existing_key, _)| existing_key == &key)
{
return Err(InitStorageDataError::DuplicateKey(format!(
"{}[{key:?}]",
slot_name.as_str()
)));
}
self.map_entries.entry(slot_name).or_default().push((key, value.into()));
Ok(())
}
pub fn set_map_values<S, E>(
&mut self,
slot_name: S,
entries: Vec<(WordValue, WordValue)>,
) -> Result<(), InitStorageDataError>
where
S: TryInto<StorageSlotName, Error = E>,
InitStorageDataError: From<E>,
{
let slot_name = slot_name.try_into().map_err(InitStorageDataError::from)?;
if self.has_value_entries_for_slot(&slot_name) {
return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
}
self.map_entries.insert(slot_name, entries);
Ok(())
}
pub fn merge_with(&mut self, other: InitStorageData) {
self.value_entries.extend(other.value_entries);
for (slot_name, entries) in other.map_entries {
self.map_entries.entry(slot_name).or_default().extend(entries);
}
}
pub fn merge_from(&mut self, other: InitStorageData) {
self.merge_with(other);
}
}
#[derive(Debug, Error)]
pub enum InitStorageDataError {
#[error("duplicate init key `{0}`")]
DuplicateKey(String),
#[error("conflicting init entries for `{0}`")]
ConflictingEntries(String),
#[error("invalid storage value name")]
InvalidValueName(#[from] StorageValueNameError),
#[error("invalid storage slot name")]
InvalidSlotName(#[from] StorageSlotNameError),
}
impl From<core::convert::Infallible> for InitStorageDataError {
fn from(err: core::convert::Infallible) -> Self {
match err {}
}
}