use core::fmt::Debug;
use crate::{
env::internal::{self, StorageType, Val},
unwrap::{UnwrapInfallible, UnwrapOptimized},
Env, IntoVal, TryFromVal,
};
#[derive(Clone)]
pub struct Storage {
env: Env,
}
impl Debug for Storage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Storage")
}
}
impl Storage {
#[inline(always)]
pub(crate) fn new(env: &Env) -> Storage {
Storage { env: env.clone() }
}
pub fn persistent(&self) -> Persistent {
assert_in_contract!(self.env);
Persistent {
storage: self.clone(),
}
}
pub fn temporary(&self) -> Temporary {
assert_in_contract!(self.env);
Temporary {
storage: self.clone(),
}
}
pub fn instance(&self) -> Instance {
assert_in_contract!(self.env);
Instance {
storage: self.clone(),
}
}
pub fn max_ttl(&self) -> u32 {
let seq = self.env.ledger().sequence();
let max = self.env.ledger().max_live_until_ledger();
max - seq
}
#[inline(always)]
pub(crate) fn has<K>(&self, key: &K, storage_type: StorageType) -> bool
where
K: IntoVal<Env, Val>,
{
self.has_internal(key.into_val(&self.env), storage_type)
}
#[inline(always)]
pub(crate) fn get<K, V>(&self, key: &K, storage_type: StorageType) -> Option<V>
where
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
let key = key.into_val(&self.env);
if self.has_internal(key, storage_type) {
let rv = self.get_internal(key, storage_type);
Some(V::try_from_val(&self.env, &rv).unwrap_optimized())
} else {
None
}
}
pub(crate) fn set<K, V>(&self, key: &K, val: &V, storage_type: StorageType)
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
{
let env = &self.env;
internal::Env::put_contract_data(env, key.into_val(env), val.into_val(env), storage_type)
.unwrap_infallible();
}
pub(crate) fn update<K, V>(
&self,
key: &K,
storage_type: StorageType,
f: impl FnOnce(Option<V>) -> V,
) -> V
where
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
V: IntoVal<Env, Val>,
{
let key = key.into_val(&self.env);
let val = self.get(&key, storage_type);
let val = f(val);
self.set(&key, &val, storage_type);
val
}
pub(crate) fn try_update<K, V, E>(
&self,
key: &K,
storage_type: StorageType,
f: impl FnOnce(Option<V>) -> Result<V, E>,
) -> Result<V, E>
where
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
V: IntoVal<Env, Val>,
{
let key = key.into_val(&self.env);
let val = self.get(&key, storage_type);
let val = f(val)?;
self.set(&key, &val, storage_type);
Ok(val)
}
pub(crate) fn extend_ttl<K>(
&self,
key: &K,
storage_type: StorageType,
threshold: u32,
extend_to: u32,
) where
K: IntoVal<Env, Val>,
{
let env = &self.env;
internal::Env::extend_contract_data_ttl(
env,
key.into_val(env),
storage_type,
threshold.into(),
extend_to.into(),
)
.unwrap_infallible();
}
#[inline(always)]
pub(crate) fn remove<K>(&self, key: &K, storage_type: StorageType)
where
K: IntoVal<Env, Val>,
{
let env = &self.env;
internal::Env::del_contract_data(env, key.into_val(env), storage_type).unwrap_infallible();
}
fn has_internal(&self, key: Val, storage_type: StorageType) -> bool {
internal::Env::has_contract_data(&self.env, key, storage_type)
.unwrap_infallible()
.into()
}
fn get_internal(&self, key: Val, storage_type: StorageType) -> Val {
internal::Env::get_contract_data(&self.env, key, storage_type).unwrap_infallible()
}
}
pub struct Persistent {
storage: Storage,
}
impl Persistent {
pub fn has<K>(&self, key: &K) -> bool
where
K: IntoVal<Env, Val>,
{
self.storage.has(key, StorageType::Persistent)
}
pub fn get<K, V>(&self, key: &K) -> Option<V>
where
V::Error: Debug,
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.get(key, StorageType::Persistent)
}
pub fn set<K, V>(&self, key: &K, val: &V)
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
{
self.storage.set(key, val, StorageType::Persistent)
}
pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.update(key, StorageType::Persistent, f)
}
pub fn try_update<K, V, E>(
&self,
key: &K,
f: impl FnOnce(Option<V>) -> Result<V, E>,
) -> Result<V, E>
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.try_update(key, StorageType::Persistent, f)
}
pub fn extend_ttl<K>(&self, key: &K, threshold: u32, extend_to: u32)
where
K: IntoVal<Env, Val>,
{
self.storage
.extend_ttl(key, StorageType::Persistent, threshold, extend_to)
}
#[inline(always)]
pub fn remove<K>(&self, key: &K)
where
K: IntoVal<Env, Val>,
{
self.storage.remove(key, StorageType::Persistent)
}
}
pub struct Temporary {
storage: Storage,
}
impl Temporary {
pub fn has<K>(&self, key: &K) -> bool
where
K: IntoVal<Env, Val>,
{
self.storage.has(key, StorageType::Temporary)
}
pub fn get<K, V>(&self, key: &K) -> Option<V>
where
V::Error: Debug,
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.get(key, StorageType::Temporary)
}
pub fn set<K, V>(&self, key: &K, val: &V)
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
{
self.storage.set(key, val, StorageType::Temporary)
}
pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.update(key, StorageType::Temporary, f)
}
pub fn try_update<K, V, E>(
&self,
key: &K,
f: impl FnOnce(Option<V>) -> Result<V, E>,
) -> Result<V, E>
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.try_update(key, StorageType::Temporary, f)
}
pub fn extend_ttl<K>(&self, key: &K, threshold: u32, extend_to: u32)
where
K: IntoVal<Env, Val>,
{
self.storage
.extend_ttl(key, StorageType::Temporary, threshold, extend_to)
}
#[inline(always)]
pub fn remove<K>(&self, key: &K)
where
K: IntoVal<Env, Val>,
{
self.storage.remove(key, StorageType::Temporary)
}
}
pub struct Instance {
storage: Storage,
}
impl Instance {
pub fn has<K>(&self, key: &K) -> bool
where
K: IntoVal<Env, Val>,
{
self.storage.has(key, StorageType::Instance)
}
pub fn get<K, V>(&self, key: &K) -> Option<V>
where
V::Error: Debug,
K: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.get(key, StorageType::Instance)
}
pub fn set<K, V>(&self, key: &K, val: &V)
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
{
self.storage.set(key, val, StorageType::Instance)
}
pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.update(key, StorageType::Instance, f)
}
pub fn try_update<K, V, E>(
&self,
key: &K,
f: impl FnOnce(Option<V>) -> Result<V, E>,
) -> Result<V, E>
where
K: IntoVal<Env, Val>,
V: IntoVal<Env, Val>,
V: TryFromVal<Env, Val>,
{
self.storage.try_update(key, StorageType::Instance, f)
}
#[inline(always)]
pub fn remove<K>(&self, key: &K)
where
K: IntoVal<Env, Val>,
{
self.storage.remove(key, StorageType::Instance)
}
pub fn extend_ttl(&self, threshold: u32, extend_to: u32) {
internal::Env::extend_current_contract_instance_and_code_ttl(
&self.storage.env,
threshold.into(),
extend_to.into(),
)
.unwrap_infallible();
}
}
#[cfg(any(test, feature = "testutils"))]
#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
mod testutils {
use super::*;
use crate::{testutils, xdr, Map, TryIntoVal};
impl testutils::storage::Instance for Instance {
fn all(&self) -> Map<Val, Val> {
let env = &self.storage.env;
let storage = env.host().get_stored_entries().unwrap();
let address: xdr::ScAddress = env.current_contract_address().try_into().unwrap();
for entry in storage {
let (k, Some((v, _))) = entry else {
continue;
};
let xdr::LedgerKey::ContractData(xdr::LedgerKeyContractData {
ref contract, ..
}) = *k
else {
continue;
};
if contract != &address {
continue;
}
let xdr::LedgerEntry {
data:
xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry {
key: xdr::ScVal::LedgerKeyContractInstance,
val:
xdr::ScVal::ContractInstance(xdr::ScContractInstance {
ref storage,
..
}),
..
}),
..
} = *v
else {
continue;
};
return match storage {
Some(map) => {
let map: Val =
Val::try_from_val(env, &xdr::ScVal::Map(Some(map.clone()))).unwrap();
map.try_into_val(env).unwrap()
}
None => Map::new(env),
};
}
panic!("contract instance for current contract address not found");
}
fn get_ttl(&self) -> u32 {
let env = &self.storage.env;
env.host()
.get_contract_instance_live_until_ledger(env.current_contract_address().to_object())
.unwrap()
.checked_sub(env.ledger().sequence())
.unwrap()
}
}
impl testutils::storage::Persistent for Persistent {
fn all(&self) -> Map<Val, Val> {
all(&self.storage.env, xdr::ContractDataDurability::Persistent)
}
fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32 {
let env = &self.storage.env;
env.host()
.get_contract_data_live_until_ledger(key.into_val(env), StorageType::Persistent)
.unwrap()
.checked_sub(env.ledger().sequence())
.unwrap()
}
}
impl testutils::storage::Temporary for Temporary {
fn all(&self) -> Map<Val, Val> {
all(&self.storage.env, xdr::ContractDataDurability::Temporary)
}
fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32 {
let env = &self.storage.env;
env.host()
.get_contract_data_live_until_ledger(key.into_val(env), StorageType::Temporary)
.unwrap()
.checked_sub(env.ledger().sequence())
.unwrap()
}
}
fn all(env: &Env, d: xdr::ContractDataDurability) -> Map<Val, Val> {
let storage = env.host().get_stored_entries().unwrap();
let mut map = Map::<Val, Val>::new(env);
for entry in storage {
let (_, Some((v, _))) = entry else {
continue;
};
let xdr::LedgerEntry {
data:
xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry {
ref key,
ref val,
durability,
..
}),
..
} = *v
else {
continue;
};
if d != durability {
continue;
}
let Ok(key) = Val::try_from_val(env, key) else {
continue;
};
let Ok(val) = Val::try_from_val(env, val) else {
continue;
};
map.set(key, val);
}
map
}
}