use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent,
TombstoneContractInfo, Trait, CodeHash, Config
};
use sp_std::prelude::*;
use sp_io::hashing::blake2_256;
use frame_support::storage::child;
use frame_support::traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReason};
use frame_support::StorageMap;
use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero};
struct OutstandingAmount<T: Trait> {
amount: BalanceOf<T>,
}
impl<T: Trait> OutstandingAmount<T> {
fn new(amount: BalanceOf<T>) -> Self {
Self { amount }
}
fn peek(&self) -> BalanceOf<T> {
self.amount
}
fn withdraw(self, account: &T::AccountId) {
if let Ok(imbalance) = T::Currency::withdraw(
account,
self.amount,
WithdrawReason::Fee.into(),
ExistenceRequirement::KeepAlive,
) {
T::RentPayment::on_unbalanced(imbalance);
}
}
}
enum Verdict<T: Trait> {
Exempt,
Kill,
Evict {
amount: Option<OutstandingAmount<T>>,
},
Charge { amount: OutstandingAmount<T> },
}
fn compute_fee_per_block<T: Trait>(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> BalanceOf<T> {
let free_storage = free_balance
.checked_div(&T::RentDepositOffset::get())
.unwrap_or_else(Zero::zero);
let empty_pairs_equivalent = contract.empty_pair_count;
let effective_storage_size = <BalanceOf<T>>::from(
contract.storage_size + T::StorageSizeOffset::get() + empty_pairs_equivalent,
)
.saturating_sub(free_storage);
effective_storage_size
.checked_mul(&T::RentByteFee::get())
.unwrap_or_else(|| <BalanceOf<T>>::max_value())
}
fn rent_budget<T: Trait>(
total_balance: &BalanceOf<T>,
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = Config::<T>::subsistence_threshold_uncached();
if *total_balance < subsistence_threshold {
return None;
}
let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);
Some(<BalanceOf<T>>::min(
contract.rent_allowance,
rent_allowed_to_charge,
))
}
fn consider_case<T: Trait>(
account: &T::AccountId,
current_block_number: T::BlockNumber,
handicap: T::BlockNumber,
contract: &AliveContractInfo<T>,
) -> Verdict<T> {
let blocks_passed = {
let effective_block_number = current_block_number.saturating_sub(handicap);
effective_block_number.saturating_sub(contract.deduct_block)
};
if blocks_passed.is_zero() {
return Verdict::Exempt;
}
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = compute_fee_per_block::<T>(&free_balance, contract);
if fee_per_block.is_zero() {
return Verdict::Exempt;
}
let rent_budget = match rent_budget::<T>(&total_balance, &free_balance, contract) {
Some(rent_budget) => rent_budget,
None => {
return Verdict::Kill;
}
};
let dues = fee_per_block
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
.unwrap_or_else(|| <BalanceOf<T>>::max_value());
let insufficient_rent = rent_budget < dues;
let dues_limited = dues.min(rent_budget);
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
account,
dues_limited,
WithdrawReason::Fee.into(),
free_balance.saturating_sub(dues_limited),
)
.is_ok();
if insufficient_rent || !can_withdraw_rent {
let amount = if can_withdraw_rent {
Some(OutstandingAmount::new(dues_limited))
} else {
None
};
return Verdict::Evict { amount };
}
return Verdict::Charge {
amount: OutstandingAmount::new(dues_limited),
};
}
fn enact_verdict<T: Trait>(
account: &T::AccountId,
alive_contract_info: AliveContractInfo<T>,
current_block_number: T::BlockNumber,
verdict: Verdict<T>,
) -> Option<ContractInfo<T>> {
match verdict {
Verdict::Exempt => return Some(ContractInfo::Alive(alive_contract_info)),
Verdict::Kill => {
<ContractInfoOf<T>>::remove(account);
child::kill_storage(
&alive_contract_info.child_trie_info(),
);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), false));
None
}
Verdict::Evict { amount } => {
if let Some(amount) = amount {
amount.withdraw(account);
}
let child_storage_root = child::root(
&alive_contract_info.child_trie_info(),
);
let tombstone = <TombstoneContractInfo<T>>::new(
&child_storage_root[..],
alive_contract_info.code_hash,
);
let tombstone_info = ContractInfo::Tombstone(tombstone);
<ContractInfoOf<T>>::insert(account, &tombstone_info);
child::kill_storage(
&alive_contract_info.child_trie_info(),
);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), true));
Some(tombstone_info)
}
Verdict::Charge { amount } => {
let contract_info = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract_info);
amount.withdraw(account);
Some(contract_info)
}
}
}
pub fn collect_rent<T: Trait>(account: &T::AccountId) -> Option<ContractInfo<T>> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return contract_info,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
enact_verdict(account, alive_contract_info, current_block_number, verdict)
}
pub fn snitch_contract_should_be_evicted<T: Trait>(
account: &T::AccountId,
handicap: T::BlockNumber,
) -> bool {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return false,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
handicap,
&alive_contract_info,
);
match verdict {
Verdict::Kill | Verdict::Evict { .. } => {
enact_verdict(account, alive_contract_info, current_block_number, verdict);
true
}
_ => false,
}
}
pub fn compute_rent_projection<T: Trait>(
account: &T::AccountId,
) -> RentProjectionResult<T::BlockNumber> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
let new_contract_info =
enact_verdict(account, alive_contract_info, current_block_number, verdict);
let alive_contract_info = match new_contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = compute_fee_per_block::<T>(&free_balance, &alive_contract_info);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction);
}
let rent_budget = rent_budget::<T>(&total_balance, &free_balance, &alive_contract_info).expect(
"the contract exists and in the alive state;
the updated balance must be greater than subsistence deposit;
this function doesn't return `None`;
qed
",
);
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
Some(blocks_left) => blocks_left,
None => {
return Ok(RentProjection::NoEviction);
}
};
let blocks_left = blocks_left.saturated_into::<u32>().into();
Ok(RentProjection::EvictionAt(
current_block_number + blocks_left,
))
}
pub fn restore_to<T: Trait>(
origin: T::AccountId,
dest: T::AccountId,
code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>,
delta: Vec<crate::exec::StorageKey>,
) -> Result<(), &'static str> {
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
.and_then(|c| c.get_alive())
.ok_or("Cannot restore from inexisting or tombstone contract")?;
let child_trie_info = origin_contract.child_trie_info();
let current_block = <frame_system::Module<T>>::block_number();
if origin_contract.last_write == Some(current_block) {
return Err("Origin TrieId written in the current block");
}
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
.and_then(|c| c.get_tombstone())
.ok_or("Cannot restore to inexisting or alive contract")?;
let last_write = if !delta.is_empty() {
Some(current_block)
} else {
origin_contract.last_write
};
let key_values_taken = delta.iter()
.filter_map(|key| {
child::get_raw(&child_trie_info, &blake2_256(key)).map(|value| {
child::kill(&child_trie_info, &blake2_256(key));
(key, value)
})
})
.collect::<Vec<_>>();
let tombstone = <TombstoneContractInfo<T>>::new(
&child::root(&child_trie_info)[..],
code_hash,
);
if tombstone != dest_tombstone {
for (key, value) in key_values_taken {
child::put_raw(&child_trie_info, &blake2_256(key), &value);
}
return Err("Tombstones don't match");
}
origin_contract.storage_size -= key_values_taken.iter()
.map(|(_, value)| value.len() as u32)
.sum::<u32>();
<ContractInfoOf<T>>::remove(&origin);
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
trie_id: origin_contract.trie_id,
storage_size: origin_contract.storage_size,
empty_pair_count: origin_contract.empty_pair_count,
total_pair_count: origin_contract.total_pair_count,
code_hash,
rent_allowance,
deduct_block: current_block,
last_write,
}));
let origin_free_balance = T::Currency::free_balance(&origin);
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
T::Currency::deposit_creating(&dest, origin_free_balance);
Ok(())
}