contracts: Charge rent for code storage (#7935)

* contracts: Implement refcounting for wasm code

* contracts: Charge rent for code storage

* contracts: Fix dispatchables erroneously refunding base costs

* Fixed typos in comments.

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Remove awkward empty line

* Fix more typos in docs

* Fix typos in docs

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Split up complicated expression

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* review: Remove unused return value

* Fix typos

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* review: Fix refcount being reset to one on re-instrumentation

* Document evictable_code parameter

* Make Executable::execute consume and store itself

* Added comments about stale values

* Disregard struct size in occupied_storage()

Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
Alexander Theißen
2021-02-04 12:01:34 +01:00
committed by GitHub
parent 5569313bd6
commit 8e49a8a6a6
17 changed files with 1903 additions and 1529 deletions
+60 -35
View File
@@ -20,7 +20,7 @@
use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent,
TombstoneContractInfo, Config, CodeHash, ConfigCache, Error,
storage::Storage,
storage::Storage, wasm::PrefabWasmModule, exec::Executable,
};
use sp_std::prelude::*;
use sp_io::hashing::blake2_256;
@@ -86,12 +86,13 @@ enum Verdict<T: Config> {
Charge { amount: OutstandingAmount<T> },
}
pub struct Rent<T>(sp_std::marker::PhantomData<T>);
pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);
impl<T> Rent<T>
impl<T, E> Rent<T, E>
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
E: Executable<T>,
{
/// Returns a fee charged per block from the contract.
///
@@ -99,10 +100,11 @@ where
/// then the fee can drop to zero.
fn compute_fee_per_block(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>
contract: &AliveContractInfo<T>,
code_size_share: u32,
) -> BalanceOf<T> {
let uncovered_by_balance = T::DepositPerStorageByte::get()
.saturating_mul(contract.storage_size.into())
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
.saturating_add(
T::DepositPerStorageItem::get()
.saturating_mul(contract.pair_count.into())
@@ -148,6 +150,7 @@ where
current_block_number: T::BlockNumber,
handicap: T::BlockNumber,
contract: &AliveContractInfo<T>,
code_size: u32,
) -> Verdict<T> {
// How much block has passed since the last deduction for the contract.
let blocks_passed = {
@@ -164,7 +167,7 @@ where
let free_balance = T::Currency::free_balance(account);
// An amount of funds to charge per block for storage taken up by the contract.
let fee_per_block = Self::compute_fee_per_block(&free_balance, contract);
let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);
if fee_per_block.is_zero() {
// The rent deposit offset reduced the fee to 0. This means that the contract
// gets the rent for free.
@@ -228,19 +231,22 @@ where
/// Enacts the given verdict and returns the updated `ContractInfo`.
///
/// `alive_contract_info` should be from the same address as `account`.
///
/// # Note
///
/// if `evictable_code` is `None` an `Evict` verdict will not be enacted. This is for
/// when calling this function during a `call` where access to the soon to be evicted
/// contract should be denied but storage should be left unmodified.
fn enact_verdict(
account: &T::AccountId,
alive_contract_info: AliveContractInfo<T>,
current_block_number: T::BlockNumber,
verdict: Verdict<T>,
allow_eviction: bool,
) -> Result<Option<ContractInfo<T>>, DispatchError> {
match verdict {
Verdict::Exempt => return Ok(Some(ContractInfo::Alive(alive_contract_info))),
Verdict::Evict { amount: _ } if !allow_eviction => {
Ok(None)
}
Verdict::Evict { amount } => {
evictable_code: Option<PrefabWasmModule<T>>,
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
match (verdict, evictable_code) {
(Verdict::Exempt, _) => return Ok(Some(alive_contract_info)),
(Verdict::Evict { amount }, Some(code)) => {
// We need to remove the trie first because it is the only operation
// that can fail and this function is called without a storage
// transaction when called through `claim_surcharge`.
@@ -261,19 +267,23 @@ where
);
let tombstone_info = ContractInfo::Tombstone(tombstone);
<ContractInfoOf<T>>::insert(account, &tombstone_info);
code.drop_from_storage();
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone()));
Ok(Some(tombstone_info))
Ok(None)
}
Verdict::Charge { amount } => {
let contract_info = ContractInfo::Alive(AliveContractInfo::<T> {
(Verdict::Evict { amount: _ }, None) => {
Ok(None)
}
(Verdict::Charge { amount }, _) => {
let contract = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
rent_payed: alive_contract_info.rent_payed.saturating_add(amount.peek()),
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract_info);
<ContractInfoOf<T>>::insert(account, &contract);
amount.withdraw(account);
Ok(Some(contract_info))
Ok(Some(contract.get_alive().expect("We just constructed it as alive. qed")))
}
}
}
@@ -283,21 +293,20 @@ where
/// This functions does **not** evict the contract. It returns `None` in case the
/// contract is in need of eviction. [`try_eviction`] must
/// be called to perform the eviction.
pub fn charge(account: &T::AccountId) -> Result<Option<ContractInfo<T>>, DispatchError> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Ok(contract_info),
Some(ContractInfo::Alive(contract)) => contract,
};
pub fn charge(
account: &T::AccountId,
contract: AliveContractInfo<T>,
code_size: u32,
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
&contract,
code_size,
);
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict, false)
Self::enact_verdict(account, contract, current_block_number, verdict, None)
}
/// Process a report that a contract under the given address should be evicted.
@@ -322,12 +331,14 @@ where
None | Some(ContractInfo::Tombstone(_)) => return Ok(None),
Some(ContractInfo::Alive(contract)) => contract,
};
let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
handicap,
&contract,
module.occupied_storage(),
);
// Enact the verdict only if the contract gets removed.
@@ -339,7 +350,9 @@ where
.map(|a| a.peek())
.unwrap_or_else(|| <BalanceOf<T>>::zero())
.saturating_add(contract.rent_payed);
Self::enact_verdict(account, contract, current_block_number, verdict, true)?;
Self::enact_verdict(
account, contract, current_block_number, verdict, Some(module),
)?;
Ok(Some(rent_payed))
}
_ => Ok(None),
@@ -367,26 +380,33 @@ where
None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let module = PrefabWasmModule::from_storage_noinstr(alive_contract_info.code_hash)
.map_err(|_| IsTombstone)?;
let code_size = module.occupied_storage();
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
code_size,
);
let new_contract_info = Self::enact_verdict(
account, alive_contract_info, current_block_number, verdict, Some(module),
);
let new_contract_info =
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict, false);
// Check what happened after enaction of the verdict.
let alive_contract_info = match new_contract_info.map_err(|_| IsTombstone)? {
None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
None => return Err(IsTombstone),
Some(contract) => contract,
};
// Compute how much would the fee per block be with the *updated* balance.
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = Self::compute_fee_per_block(&free_balance, &alive_contract_info);
let fee_per_block = Self::compute_fee_per_block(
&free_balance, &alive_contract_info, code_size,
);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction);
}
@@ -418,6 +438,7 @@ where
/// Restores the destination account using the origin as prototype.
///
/// The restoration will be performed iff:
/// - the supplied code_hash does still exist on-chain
/// - origin exists and is alive,
/// - the origin's storage is not written in the current block
/// - the restored account has tombstone
@@ -455,6 +476,9 @@ where
origin_contract.last_write
};
// Fails if the code hash does not exist on chain
E::add_user(code_hash)?;
// We are allowed to eagerly modify storage even though the function can
// fail later due to tombstones not matching. This is because the restoration
// is always called from a contract and therefore in a storage transaction.
@@ -483,6 +507,7 @@ where
origin_contract.storage_size -= bytes_taken;
<ContractInfoOf<T>>::remove(&origin);
E::remove_user(origin_contract.code_hash);
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
trie_id: origin_contract.trie_id,
storage_size: origin_contract.storage_size,