pallet-contracts: Rent projection RPC (#4754)

* Initial approach

* Introduce the pallet-contracts-common crate

* Add rent::compute_rent_projection

* Wire everything together

* Fix build error.

* Rename EvictionDate → EvictionAt.

* Clean.

* Renaming and cleaning.

* Add documentation for rent_projection RPC.

* Add documentation for rent_projection runtime API.

* Refactor rent_budget.

Merge it with subsistence_treshold.

* Bump impl_version

* Constrain RPC impl with Block::Header::Number.

* Rename pallet-contracts-common into -primitives

* Add a comment for `compute_rent_projection` on the usage

* Small tidying
This commit is contained in:
Sergei Pepyakin
2020-01-31 15:22:25 +01:00
committed by GitHub
parent 3018bfe0e9
commit df6ef1780f
13 changed files with 339 additions and 114 deletions
+13
View File
@@ -3196,6 +3196,7 @@ dependencies = [
"pallet-balances 2.0.0",
"pallet-collective 2.0.0",
"pallet-contracts 2.0.0",
"pallet-contracts-primitives 2.0.0",
"pallet-contracts-rpc-runtime-api 0.8.0",
"pallet-democracy 2.0.0",
"pallet-elections-phragmen 2.0.0",
@@ -3631,6 +3632,7 @@ dependencies = [
"frame-system 2.0.0",
"hex-literal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pallet-balances 2.0.0",
"pallet-contracts-primitives 2.0.0",
"pallet-randomness-collective-flip 2.0.0",
"pallet-timestamp 2.0.0",
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3646,6 +3648,15 @@ dependencies = [
"wasmi-validation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pallet-contracts-primitives"
version = "2.0.0"
dependencies = [
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"sp-runtime 2.0.0",
"sp-std 2.0.0",
]
[[package]]
name = "pallet-contracts-rpc"
version = "0.8.0"
@@ -3653,6 +3664,7 @@ dependencies = [
"jsonrpc-core 14.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core-client 14.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-derive 14.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pallet-contracts-primitives 2.0.0",
"pallet-contracts-rpc-runtime-api 0.8.0",
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3668,6 +3680,7 @@ dependencies = [
name = "pallet-contracts-rpc-runtime-api"
version = "0.8.0"
dependencies = [
"pallet-contracts-primitives 2.0.0",
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"sp-api 2.0.0",
"sp-runtime 2.0.0",
+2 -2
View File
@@ -31,7 +31,7 @@
use std::sync::Arc;
use node_primitives::{Block, AccountId, Index, Balance};
use node_primitives::{Block, BlockNumber, AccountId, Index, Balance};
use node_runtime::UncheckedExtrinsic;
use sp_api::ProvideRuntimeApi;
use sp_transaction_pool::TransactionPool;
@@ -66,7 +66,7 @@ pub fn create<C, P, M, F>(
C: sc_client::blockchain::HeaderBackend<Block>,
C: Send + Sync + 'static,
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>,
C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance>,
C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber>,
C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance, UncheckedExtrinsic>,
F: sc_client::light::fetcher::Fetcher<Block> + 'static,
P: TransactionPool + 'static,
+3 -1
View File
@@ -42,6 +42,7 @@ pallet-babe = { version = "2.0.0", default-features = false, path = "../../../fr
pallet-balances = { version = "2.0.0", default-features = false, path = "../../../frame/balances" }
pallet-collective = { version = "2.0.0", default-features = false, path = "../../../frame/collective" }
pallet-contracts = { version = "2.0.0", default-features = false, path = "../../../frame/contracts" }
pallet-contracts-primitives = { version = "2.0.0", default-features = false, path = "../../../frame/contracts/common/" }
pallet-contracts-rpc-runtime-api = { version = "0.8.0", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" }
pallet-democracy = { version = "2.0.0", default-features = false, path = "../../../frame/democracy" }
pallet-elections-phragmen = { version = "2.0.0", default-features = false, path = "../../../frame/elections-phragmen" }
@@ -83,8 +84,9 @@ std = [
"sp-block-builder/std",
"codec/std",
"pallet-collective/std",
"pallet-contracts-rpc-runtime-api/std",
"pallet-contracts/std",
"pallet-contracts-primitives/std",
"pallet-contracts-rpc-runtime-api/std",
"pallet-democracy/std",
"pallet-elections-phragmen/std",
"frame-executive/std",
+14 -19
View File
@@ -81,7 +81,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 210,
impl_version: 0,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
};
@@ -746,7 +746,9 @@ impl_runtime_apis! {
}
}
impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance> for Runtime {
impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber>
for Runtime
{
fn call(
origin: AccountId,
dest: AccountId,
@@ -754,13 +756,8 @@ impl_runtime_apis! {
gas_limit: u64,
input_data: Vec<u8>,
) -> ContractExecResult {
let exec_result = Contracts::bare_call(
origin,
dest.into(),
value,
gas_limit,
input_data,
);
let exec_result =
Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data);
match exec_result {
Ok(v) => ContractExecResult::Success {
status: v.status,
@@ -773,16 +770,14 @@ impl_runtime_apis! {
fn get_storage(
address: AccountId,
key: [u8; 32],
) -> pallet_contracts_rpc_runtime_api::GetStorageResult {
Contracts::get_storage(address, key).map_err(|rpc_err| {
use pallet_contracts::GetStorageError;
use pallet_contracts_rpc_runtime_api::{GetStorageError as RpcGetStorageError};
/// Map the contract error into the RPC layer error.
match rpc_err {
GetStorageError::ContractDoesntExist => RpcGetStorageError::ContractDoesntExist,
GetStorageError::IsTombstone => RpcGetStorageError::IsTombstone,
}
})
) -> pallet_contracts_primitives::GetStorageResult {
Contracts::get_storage(address, key)
}
fn rent_projection(
address: AccountId,
) -> pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
Contracts::rent_projection(address)
}
}
+2
View File
@@ -18,6 +18,7 @@ sp-std = { version = "2.0.0", default-features = false, path = "../../primitives
sp-sandbox = { version = "0.8.0", default-features = false, path = "../../primitives/sandbox" }
frame-support = { version = "2.0.0", default-features = false, path = "../support" }
frame-system = { version = "2.0.0", default-features = false, path = "../system" }
pallet-contracts-primitives = { version = "2.0.0", default-features = false, path = "common" }
[dev-dependencies]
wabt = "0.9.2"
@@ -42,4 +43,5 @@ std = [
"parity-wasm/std",
"pwasm-utils/std",
"wasmi-validation/std",
"pallet-contracts-primitives/std",
]
@@ -0,0 +1,19 @@
[package]
name = "pallet-contracts-primitives"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
# This crate should not rely on any of the frame primitives.
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] }
sp-std = { version = "2.0.0", default-features = false, path = "../../../primitives/std" }
sp-runtime = { version = "2.0.0", default-features = false, path = "../../../primitives/runtime" }
[features]
default = ["std"]
std = [
"codec/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -0,0 +1,47 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A crate that hosts a common definitions that are relevent for the pallet-contracts.
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::prelude::*;
/// A result type of a get storage call.
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
/// The possible errors that can happen querying the storage of a contract.
#[derive(Eq, PartialEq, codec::Encode, codec::Decode, sp_runtime::RuntimeDebug)]
pub enum ContractAccessError {
/// The given address doesn't point to a contract.
DoesntExist,
/// The specified contract is a tombstone and thus cannot have any storage.
IsTombstone,
}
/// A result type of a `rent_projection` call.
pub type RentProjectionResult<BlockNumber> =
Result<RentProjection<BlockNumber>, ContractAccessError>;
#[derive(Eq, PartialEq, codec::Encode, codec::Decode, sp_runtime::RuntimeDebug)]
pub enum RentProjection<BlockNumber> {
/// Eviction is projected to happen at the specified block number.
EvictionAt(BlockNumber),
/// No eviction is scheduled.
///
/// E.g. because the contract accumulated enough funds to offset the rent storage costs.
NoEviction,
}
+1
View File
@@ -16,6 +16,7 @@ sp-rpc = { version = "2.0.0", path = "../../../primitives/rpc" }
serde = { version = "1.0.101", features = ["derive"] }
sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" }
sp-api = { version = "2.0.0", path = "../../../primitives/api" }
pallet-contracts-primitives = { version = "2.0.0", path = "../common" }
pallet-contracts-rpc-runtime-api = { version = "0.8.0", path = "./runtime-api" }
[dev-dependencies]
@@ -10,6 +10,7 @@ sp-api = { version = "2.0.0", default-features = false, path = "../../../../prim
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] }
sp-std = { version = "2.0.0", default-features = false, path = "../../../../primitives/std" }
sp-runtime = { version = "2.0.0", default-features = false, path = "../../../../primitives/runtime" }
pallet-contracts-primitives = { version = "2.0.0", default-features = false, path = "../../common" }
[features]
default = ["std"]
@@ -18,4 +19,5 @@ std = [
"codec/std",
"sp-std/std",
"sp-runtime/std",
"pallet-contracts-primitives/std",
]
@@ -22,9 +22,10 @@
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::vec::Vec;
use codec::{Encode, Decode, Codec};
use codec::{Codec, Decode, Encode};
use pallet_contracts_primitives::{GetStorageResult, RentProjectionResult};
use sp_runtime::RuntimeDebug;
use sp_std::vec::Vec;
/// A result of execution of a contract.
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
@@ -44,25 +45,12 @@ pub enum ContractExecResult {
Error,
}
/// A result type of the get storage call.
///
/// See [`ContractsApi::get_storage`] for more info.
pub type GetStorageResult = Result<Option<Vec<u8>>, GetStorageError>;
/// The possible errors that can happen querying the storage of a contract.
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
pub enum GetStorageError {
/// The given address doesn't point on a contract.
ContractDoesntExist,
/// The specified contract is a tombstone and thus cannot have any storage.
IsTombstone,
}
sp_api::decl_runtime_apis! {
/// The API to interact with contracts without using executive.
pub trait ContractsApi<AccountId, Balance> where
pub trait ContractsApi<AccountId, Balance, BlockNumber> where
AccountId: Codec,
Balance: Codec,
BlockNumber: Codec,
{
/// Perform a call from a specified account to a given contract.
///
@@ -85,5 +73,13 @@ sp_api::decl_runtime_apis! {
address: AccountId,
key: [u8; 32],
) -> GetStorageResult;
/// Returns the projected time a given contract will be able to sustain paying its rent.
///
/// The returned projection is relevent for the current block, i.e. it is as if the contract
/// was accessed at the current block.
///
/// Returns `Err` if the contract is in a tombstone state or doesn't exist.
fn rent_projection(address: AccountId) -> RentProjectionResult<BlockNumber>;
}
}
+84 -40
View File
@@ -18,19 +18,23 @@
use std::sync::Arc;
use sp_blockchain::HeaderBackend;
use codec::Codec;
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use sp_core::{H256, Bytes};
use sp_rpc::number;
use pallet_contracts_primitives::RentProjection;
use serde::{Deserialize, Serialize};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_core::{Bytes, H256};
use sp_rpc::number;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT},
};
pub use self::gen_client::Client as ContractsClient;
pub use pallet_contracts_rpc_runtime_api::{
self as runtime_api, ContractExecResult, ContractsApi as ContractsRuntimeApi, GetStorageResult,
self as runtime_api, ContractExecResult, ContractsApi as ContractsRuntimeApi,
};
const RUNTIME_ERROR: i64 = 1;
@@ -46,13 +50,13 @@ const CONTRACT_IS_A_TOMBSTONE: i64 = 3;
/// https://docs.google.com/spreadsheets/d/1h0RqncdqiWI4KgxO0z9JIpZEJESXjX_ZCK6LFX6veDo/view
const GAS_PER_SECOND: u64 = 1_000_000_000;
/// A private newtype for converting `GetStorageError` into an RPC error.
struct GetStorageError(runtime_api::GetStorageError);
impl From<GetStorageError> for Error {
fn from(e: GetStorageError) -> Error {
use runtime_api::GetStorageError::*;
/// A private newtype for converting `ContractAccessError` into an RPC error.
struct ContractAccessError(pallet_contracts_primitives::ContractAccessError);
impl From<ContractAccessError> for Error {
fn from(e: ContractAccessError) -> Error {
use pallet_contracts_primitives::ContractAccessError::*;
match e.0 {
ContractDoesntExist => Error {
DoesntExist => Error {
code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST),
message: "The specified contract doesn't exist.".into(),
data: None,
@@ -61,7 +65,7 @@ impl From<GetStorageError> for Error {
code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE),
message: "The contract is a tombstone and doesn't have any storage.".into(),
data: None,
}
},
}
}
}
@@ -97,19 +101,18 @@ pub enum RpcContractExecResult {
impl From<ContractExecResult> for RpcContractExecResult {
fn from(r: ContractExecResult) -> Self {
match r {
ContractExecResult::Success { status, data } => {
RpcContractExecResult::Success { status, data: data.into() }
},
ContractExecResult::Error => {
RpcContractExecResult::Error(())
ContractExecResult::Success { status, data } => RpcContractExecResult::Success {
status,
data: data.into(),
},
ContractExecResult::Error => RpcContractExecResult::Error(()),
}
}
}
/// Contracts RPC methods.
#[rpc]
pub trait ContractsApi<BlockHash, AccountId, Balance> {
pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance> {
/// Executes a call to a contract.
///
/// This call is performed locally without submitting any transactions. Thus executing this
@@ -132,6 +135,19 @@ pub trait ContractsApi<BlockHash, AccountId, Balance> {
key: H256,
at: Option<BlockHash>,
) -> Result<Option<Bytes>>;
/// Returns the projected time a given contract will be able to sustain paying its rent.
///
/// The returned projection is relevent for the given block, i.e. it is as if the contract was
/// accessed at the beginning of that block.
///
/// Returns `None` if the contract is exempted from rent.
#[rpc(name = "contracts_rentProjection")]
fn rent_projection(
&self,
address: AccountId,
at: Option<BlockHash>,
) -> Result<Option<BlockNumber>>;
}
/// An implementation of contract specific RPC methods.
@@ -149,13 +165,22 @@ impl<C, B> Contracts<C, B> {
}
}
}
impl<C, Block, AccountId, Balance> ContractsApi<<Block as BlockT>::Hash, AccountId, Balance>
for Contracts<C, Block>
impl<C, Block, AccountId, Balance>
ContractsApi<
<Block as BlockT>::Hash,
<<Block as BlockT>::Header as HeaderT>::Number,
AccountId,
Balance,
> for Contracts<C, Block>
where
Block: BlockT,
C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
C::Api: ContractsRuntimeApi<Block, AccountId, Balance>,
C::Api: ContractsRuntimeApi<
Block,
AccountId,
Balance,
<<Block as BlockT>::Header as HeaderT>::Number,
>,
AccountId: Codec,
Balance: Codec,
{
@@ -188,8 +213,7 @@ where
code: ErrorCode::InvalidParams,
message: format!(
"Requested gas limit is greater than maximum allowed: {} > {}",
gas_limit,
max_gas_limit
gas_limit, max_gas_limit
),
data: None,
});
@@ -197,11 +221,7 @@ where
let exec_result = api
.call(&at, origin, dest, value, gas_limit, input_data.to_vec())
.map_err(|e| Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
message: "Runtime trapped while executing a contract.".into(),
data: Some(format!("{:?}", e).into()),
})?;
.map_err(|e| runtime_error_into_rpc_err(e))?;
Ok(exec_result.into())
}
@@ -217,19 +237,43 @@ where
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let get_storage_result = api
let result = api
.get_storage(&at, address, key.into())
.map_err(|e|
// Handle general API calling errors.
Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
message: "Runtime trapped while querying storage.".into(),
data: Some(format!("{:?}", e).into()),
})?
.map_err(GetStorageError)?
.map_err(|e| runtime_error_into_rpc_err(e))?
.map_err(ContractAccessError)?
.map(Bytes);
Ok(get_storage_result)
Ok(result)
}
fn rent_projection(
&self,
address: AccountId,
at: Option<<Block as BlockT>::Hash>,
) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let result = api
.rent_projection(&at, address)
.map_err(|e| runtime_error_into_rpc_err(e))?
.map_err(ContractAccessError)?;
Ok(match result {
RentProjection::NoEviction => None,
RentProjection::EvictionAt(block_num) => Some(block_num),
})
}
}
/// Converts a runtime trap into an RPC error.
fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {
Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
message: "Runtime trapped".into(),
data: Some(format!("{:?}", err).into()),
}
}
@@ -240,7 +284,7 @@ mod tests {
#[test]
fn should_serialize_deserialize_properly() {
fn test(expected: &str) {
let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, expected);
}
+10 -11
View File
@@ -128,6 +128,7 @@ use frame_support::{
use frame_support::traits::{OnFreeBalanceZero, OnUnbalanced, Currency, Get, Time, Randomness};
use frame_system::{self as system, ensure_signed, RawOrigin, ensure_root};
use sp_core::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX;
use pallet_contracts_primitives::{RentProjection, ContractAccessError};
pub type CodeHash<T> = <T as frame_system::Trait>::Hash;
pub type TrieId = Vec<u8>;
@@ -680,14 +681,6 @@ decl_module! {
}
}
/// The possible errors that can happen querying the storage of a contract.
pub enum GetStorageError {
/// The given address doesn't point on a contract.
ContractDoesntExist,
/// The specified contract is a tombstone and thus cannot have any storage.
IsTombstone,
}
/// Public APIs provided by the contracts module.
impl<T: Trait> Module<T> {
/// Perform a call to a specified contract.
@@ -710,11 +703,11 @@ impl<T: Trait> Module<T> {
pub fn get_storage(
address: T::AccountId,
key: [u8; 32],
) -> sp_std::result::Result<Option<Vec<u8>>, GetStorageError> {
) -> sp_std::result::Result<Option<Vec<u8>>, ContractAccessError> {
let contract_info = <ContractInfoOf<T>>::get(&address)
.ok_or(GetStorageError::ContractDoesntExist)?
.ok_or(ContractAccessError::DoesntExist)?
.get_alive()
.ok_or(GetStorageError::IsTombstone)?;
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = AccountDb::<T>::get_storage(
&DirectAccountDb,
@@ -724,6 +717,12 @@ impl<T: Trait> Module<T> {
);
Ok(maybe_value)
}
pub fn rent_projection(
address: T::AccountId,
) -> sp_std::result::Result<RentProjection<T::BlockNumber>, ContractAccessError> {
rent::compute_rent_projection::<T>(&address)
}
}
impl<T: Trait> Module<T> {
+129 -24
View File
@@ -21,6 +21,7 @@ use crate::{
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};
/// The amount to charge.
@@ -74,9 +75,57 @@ enum Verdict<T: Trait> {
amount: Option<OutstandingAmount<T>>,
},
/// Everything is OK, we just only take some charge.
Charge {
amount: OutstandingAmount<T>,
},
Charge { amount: OutstandingAmount<T> },
}
/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
/// then the fee can drop to zero.
fn compute_fee_per_block<T: Trait>(
balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> BalanceOf<T> {
let free_storage = balance
.checked_div(&T::RentDepositOffset::get())
.unwrap_or_else(Zero::zero);
let effective_storage_size =
<BalanceOf<T>>::from(contract.storage_size).saturating_sub(free_storage);
effective_storage_size
.checked_mul(&T::RentByteFee::get())
.unwrap_or(<BalanceOf<T>>::max_value())
}
/// Subsistence threshold is the extension of the minimum balance (aka existential deposit) by the
/// tombstone deposit, required for leaving a tombstone.
///
/// Rent mechanism cannot make the balance lower than subsistence threshold.
fn subsistence_threshold<T: Trait>() -> BalanceOf<T> {
T::Currency::minimum_balance() + T::TombstoneDeposit::get()
}
/// Returns amount of funds available to consume by rent mechanism.
///
/// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot make
/// the balance lower than [`subsistence_threshold`].
///
/// In case the balance is below the subsistence threshold, this function returns `None`.
fn rent_budget<T: Trait>(
balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = subsistence_threshold::<T>();
if *balance < subsistence_threshold {
return None;
}
let rent_allowed_to_charge = *balance - subsistence_threshold;
Some(<BalanceOf<T>>::min(
contract.rent_allowance,
rent_allowed_to_charge,
))
}
/// Consider the case for rent payment of the given account and returns a `Verdict`.
@@ -103,37 +152,27 @@ fn consider_case<T: Trait>(
let 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 = {
let free_storage = balance
.checked_div(&T::RentDepositOffset::get())
.unwrap_or_else(Zero::zero);
let effective_storage_size =
<BalanceOf<T>>::from(contract.storage_size).saturating_sub(free_storage);
effective_storage_size
.checked_mul(&T::RentByteFee::get())
.unwrap_or(<BalanceOf<T>>::max_value())
};
let fee_per_block = compute_fee_per_block::<T>(&balance, contract);
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.
return Verdict::Exempt;
}
// The minimal amount of funds required for a contract not to be evicted.
let subsistence_threshold = T::Currency::minimum_balance() + T::TombstoneDeposit::get();
if balance < subsistence_threshold {
// The contract cannot afford to leave a tombstone, so remove the contract info altogether.
return Verdict::Kill;
}
let rent_budget = match rent_budget::<T>(&balance, contract) {
Some(rent_budget) => rent_budget,
None => {
// The contract's balance is already below subsistence threshold. That indicates that
// the contract cannot afford to leave a tombstone.
//
// So cleanly wipe the contract.
return Verdict::Kill;
}
};
let dues = fee_per_block
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
.unwrap_or(<BalanceOf<T>>::max_value());
let rent_budget = contract.rent_allowance.min(balance - subsistence_threshold);
let insufficient_rent = rent_budget < dues;
// If the rent payment cannot be withdrawn due to locks on the account balance, then evict the
@@ -284,3 +323,69 @@ pub fn snitch_contract_should_be_evicted<T: Trait>(
_ => false,
}
}
/// Returns the projected time a given contract will be able to sustain paying its rent. The
/// returned projection is relevent for the current block, i.e. it is as if the contract was
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
/// evicted before or as a result of the rent collection.
///
/// The returned value is only an estimation. It doesn't take into account any top ups, changing the
/// rent allowance, or any problems coming from withdrawing the dues.
///
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
/// compute the projection. This function is only used for implementation of an RPC method through
/// `RuntimeApi` meaning that the changes will be discarded anyway.
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);
// Check what happened after enaction of the verdict.
let alive_contract_info = match new_contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
// Compute how much would the fee per block be with the *updated* balance.
let balance = T::Currency::free_balance(account);
let fee_per_block = compute_fee_per_block::<T>(&balance, &alive_contract_info);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction);
}
// Then compute how much the contract will sustain under these circumstances.
let rent_budget = rent_budget::<T>(&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 => {
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
// there is an overflow. This cannot happen with integers though. Return
// `NoEviction` here just in case.
return Ok(RentProjection::NoEviction);
}
};
let blocks_left = blocks_left.saturated_into::<u32>().into();
Ok(RentProjection::EvictionAt(
current_block_number + blocks_left,
))
}