mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
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:
Generated
+13
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user