pub mod meter;
use crate::{
exec::{AccountIdOf, Key},
weights::WeightInfo,
BalanceOf, CodeHash, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter,
Error, TrieId, SENTINEL,
};
use alloc::vec::Vec;
use codec::{Decode, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use frame_support::{
storage::child::{self, ChildInfo},
weights::{Weight, WeightMeter},
CloneNoBound, DefaultNoBound,
};
use scale_info::TypeInfo;
use sp_core::Get;
use sp_io::KillStorageResult;
use sp_runtime::{
traits::{Hash, Saturating, Zero},
BoundedBTreeMap, DispatchError, DispatchResult, RuntimeDebug,
};
use self::meter::Diff;
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
pub trie_id: TrieId,
pub code_hash: CodeHash<T>,
storage_bytes: u32,
storage_items: u32,
pub storage_byte_deposit: BalanceOf<T>,
storage_item_deposit: BalanceOf<T>,
storage_base_deposit: BalanceOf<T>,
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
}
impl<T: Config> ContractInfo<T> {
pub fn new(
account: &AccountIdOf<T>,
nonce: u64,
code_hash: CodeHash<T>,
) -> Result<Self, DispatchError> {
if <ContractInfoOf<T>>::contains_key(account) {
return Err(Error::<T>::DuplicateContract.into())
}
let trie_id = {
let buf = (account, nonce).using_encoded(T::Hashing::hash);
buf.as_ref()
.to_vec()
.try_into()
.expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
};
let contract = Self {
trie_id,
code_hash,
storage_bytes: 0,
storage_items: 0,
storage_byte_deposit: Zero::zero(),
storage_item_deposit: Zero::zero(),
storage_base_deposit: Zero::zero(),
delegate_dependencies: Default::default(),
};
Ok(contract)
}
pub fn delegate_dependencies_count(&self) -> usize {
self.delegate_dependencies.len()
}
pub fn child_trie_info(&self) -> ChildInfo {
ChildInfo::new_default(self.trie_id.as_ref())
}
pub fn extra_deposit(&self) -> BalanceOf<T> {
self.storage_byte_deposit.saturating_add(self.storage_item_deposit)
}
pub fn total_deposit(&self) -> BalanceOf<T> {
self.extra_deposit().saturating_add(self.storage_base_deposit)
}
pub fn storage_base_deposit(&self) -> BalanceOf<T> {
self.storage_base_deposit
}
pub fn read(&self, key: &Key<T>) -> Option<Vec<u8>> {
child::get_raw(&self.child_trie_info(), key.hash().as_slice())
}
pub fn size(&self, key: &Key<T>) -> Option<u32> {
child::len(&self.child_trie_info(), key.hash().as_slice())
}
pub fn write(
&self,
key: &Key<T>,
new_value: Option<Vec<u8>>,
storage_meter: Option<&mut meter::NestedMeter<T>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
let hashed_key = key.hash();
self.write_raw(&hashed_key, new_value, storage_meter, take)
}
#[cfg(feature = "runtime-benchmarks")]
pub fn bench_write_raw(
&self,
key: &[u8],
new_value: Option<Vec<u8>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
self.write_raw(key, new_value, None, take)
}
fn write_raw(
&self,
key: &[u8],
new_value: Option<Vec<u8>>,
storage_meter: Option<&mut meter::NestedMeter<T>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
let child_trie_info = &self.child_trie_info();
let (old_len, old_value) = if take {
let val = child::get_raw(child_trie_info, key);
(val.as_ref().map(|v| v.len() as u32), val)
} else {
(child::len(child_trie_info, key), None)
};
if let Some(storage_meter) = storage_meter {
let mut diff = meter::Diff::default();
match (old_len, new_value.as_ref().map(|v| v.len() as u32)) {
(Some(old_len), Some(new_len)) =>
if new_len > old_len {
diff.bytes_added = new_len - old_len;
} else {
diff.bytes_removed = old_len - new_len;
},
(None, Some(new_len)) => {
diff.bytes_added = new_len;
diff.items_added = 1;
},
(Some(old_len), None) => {
diff.bytes_removed = old_len;
diff.items_removed = 1;
},
(None, None) => (),
}
storage_meter.charge(&diff);
}
match &new_value {
Some(new_value) => child::put_raw(child_trie_info, key, new_value),
None => child::kill(child_trie_info, key),
}
Ok(match (old_len, old_value) {
(None, _) => WriteOutcome::New,
(Some(old_len), None) => WriteOutcome::Overwritten(old_len),
(Some(_), Some(old_value)) => WriteOutcome::Taken(old_value),
})
}
pub fn update_base_deposit(&mut self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
let info_deposit =
Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() }
.update_contract::<T>(None)
.charge_or_zero();
let upload_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit());
let deposit = info_deposit.saturating_add(upload_deposit);
self.storage_base_deposit = deposit;
deposit
}
pub fn lock_delegate_dependency(
&mut self,
code_hash: CodeHash<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
self.delegate_dependencies
.try_insert(code_hash, amount)
.map_err(|_| Error::<T>::MaxDelegateDependenciesReached)?
.map_or(Ok(()), |_| Err(Error::<T>::DelegateDependencyAlreadyExists))
.map_err(Into::into)
}
pub fn unlock_delegate_dependency(
&mut self,
code_hash: &CodeHash<T>,
) -> Result<BalanceOf<T>, DispatchError> {
self.delegate_dependencies
.remove(code_hash)
.ok_or(Error::<T>::DelegateDependencyNotFound.into())
}
pub fn delegate_dependencies(
&self,
) -> &BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies> {
&self.delegate_dependencies
}
pub fn queue_trie_for_deletion(&self) {
DeletionQueueManager::<T>::load().insert(self.trie_id.clone());
}
pub fn deletion_budget(meter: &WeightMeter) -> (Weight, u32) {
let base_weight = T::WeightInfo::on_process_deletion_queue_batch();
let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) -
T::WeightInfo::on_initialize_per_trie_key(0);
let key_budget = meter
.limit()
.saturating_sub(base_weight)
.checked_div_per_component(&weight_per_key)
.unwrap_or(0) as u32;
(weight_per_key, key_budget)
}
pub fn process_deletion_queue_batch(meter: &mut WeightMeter) {
if meter.try_consume(T::WeightInfo::on_process_deletion_queue_batch()).is_err() {
return
};
let mut queue = <DeletionQueueManager<T>>::load();
if queue.is_empty() {
return;
}
let (weight_per_key, budget) = Self::deletion_budget(&meter);
let mut remaining_key_budget = budget;
while remaining_key_budget > 0 {
let Some(entry) = queue.next() else { break };
#[allow(deprecated)]
let outcome = child::kill_storage(
&ChildInfo::new_default(&entry.trie_id),
Some(remaining_key_budget),
);
match outcome {
KillStorageResult::SomeRemaining(keys_removed) => {
remaining_key_budget.saturating_reduce(keys_removed);
break
},
KillStorageResult::AllRemoved(keys_removed) => {
entry.remove();
remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed.max(1));
},
};
}
meter.consume(weight_per_key.saturating_mul(u64::from(budget - remaining_key_budget)))
}
pub fn load_code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
<ContractInfoOf<T>>::get(account).map(|i| i.code_hash)
}
}
#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))]
pub enum WriteOutcome {
New,
Overwritten(u32),
Taken(Vec<u8>),
}
impl WriteOutcome {
pub fn old_len(&self) -> u32 {
match self {
Self::New => 0,
Self::Overwritten(len) => *len,
Self::Taken(value) => value.len() as u32,
}
}
pub fn old_len_with_sentinel(&self) -> u32 {
match self {
Self::New => SENTINEL,
Self::Overwritten(len) => *len,
Self::Taken(value) => value.len() as u32,
}
}
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct DeletionQueueManager<T: Config> {
insert_counter: u32,
delete_counter: u32,
_phantom: PhantomData<T>,
}
struct DeletionQueueEntry<'a, T: Config> {
trie_id: TrieId,
queue: &'a mut DeletionQueueManager<T>,
}
impl<'a, T: Config> DeletionQueueEntry<'a, T> {
fn remove(self) {
<DeletionQueue<T>>::remove(self.queue.delete_counter);
self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1);
<DeletionQueueCounter<T>>::set(self.queue.clone());
}
}
impl<T: Config> DeletionQueueManager<T> {
fn load() -> Self {
<DeletionQueueCounter<T>>::get()
}
fn is_empty(&self) -> bool {
self.insert_counter.wrapping_sub(self.delete_counter) == 0
}
fn insert(&mut self, trie_id: TrieId) {
<DeletionQueue<T>>::insert(self.insert_counter, trie_id);
self.insert_counter = self.insert_counter.wrapping_add(1);
<DeletionQueueCounter<T>>::set(self.clone());
}
fn next(&mut self) -> Option<DeletionQueueEntry<T>> {
if self.is_empty() {
return None
}
let entry = <DeletionQueue<T>>::get(self.delete_counter);
entry.map(|trie_id| DeletionQueueEntry { trie_id, queue: self })
}
}
#[cfg(test)]
impl<T: Config> DeletionQueueManager<T> {
pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self {
Self { insert_counter, delete_counter, _phantom: Default::default() }
}
pub fn as_test_tuple(&self) -> (u32, u32) {
(self.insert_counter, self.delete_counter)
}
}