contracts: Remove state rent (#9669)

* Remove storage rent

* Add storage migration

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Add migration for deletetion queue

* Fix compilation

* Increase gas supplied to out_of_gas to be sure that it won't deplete too early

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs

Co-authored-by: Parity Bot <admin@parity.io>
This commit is contained in:
Alexander Theißen
2021-09-06 18:40:58 +02:00
committed by GitHub
parent 13f3e25ebb
commit 522e77e243
20 changed files with 1158 additions and 4641 deletions
+4 -23
View File
@@ -822,16 +822,10 @@ impl pallet_tips::Config for Runtime {
} }
parameter_types! { parameter_types! {
pub TombstoneDeposit: Balance = deposit( pub ContractDeposit: Balance = deposit(
1, 1,
<pallet_contracts::Pallet<Runtime>>::contract_info_size(), <pallet_contracts::Pallet<Runtime>>::contract_info_size(),
); );
pub DepositPerContract: Balance = TombstoneDeposit::get();
pub const DepositPerStorageByte: Balance = deposit(0, 1);
pub const DepositPerStorageItem: Balance = deposit(1, 0);
pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS);
pub const SurchargeReward: Balance = 150 * MILLICENTS;
pub const SignedClaimHandicap: u32 = 2;
pub const MaxValueSize: u32 = 16 * 1024; pub const MaxValueSize: u32 = 16 * 1024;
// The lazy deletion runs inside on_initialize. // The lazy deletion runs inside on_initialize.
pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO *
@@ -858,14 +852,7 @@ impl pallet_contracts::Config for Runtime {
/// change because that would break already deployed contracts. The `Call` structure itself /// change because that would break already deployed contracts. The `Call` structure itself
/// is not allowed to change the indices of existing pallets, too. /// is not allowed to change the indices of existing pallets, too.
type CallFilter = Nothing; type CallFilter = Nothing;
type RentPayment = (); type ContractDeposit = ContractDeposit;
type SignedClaimHandicap = SignedClaimHandicap;
type TombstoneDeposit = TombstoneDeposit;
type DepositPerContract = DepositPerContract;
type DepositPerStorageByte = DepositPerStorageByte;
type DepositPerStorageItem = DepositPerStorageItem;
type RentFraction = RentFraction;
type SurchargeReward = SurchargeReward;
type CallStack = [pallet_contracts::Frame<Self>; 31]; type CallStack = [pallet_contracts::Frame<Self>; 31];
type WeightPrice = pallet_transaction_payment::Pallet<Self>; type WeightPrice = pallet_transaction_payment::Pallet<Self>;
type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>; type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
@@ -1461,9 +1448,9 @@ impl_runtime_apis! {
code: pallet_contracts_primitives::Code<Hash>, code: pallet_contracts_primitives::Code<Hash>,
data: Vec<u8>, data: Vec<u8>,
salt: Vec<u8>, salt: Vec<u8>,
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber> ) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId>
{ {
Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true, true) Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true)
} }
fn get_storage( fn get_storage(
@@ -1472,12 +1459,6 @@ impl_runtime_apis! {
) -> pallet_contracts_primitives::GetStorageResult { ) -> pallet_contracts_primitives::GetStorageResult {
Contracts::get_storage(address, key) Contracts::get_storage(address, key)
} }
fn rent_projection(
address: AccountId,
) -> pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
Contracts::rent_projection(address)
}
} }
impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<
+3 -29
View File
@@ -70,35 +70,17 @@ pub struct ContractResult<T> {
pub type ContractExecResult = ContractResult<Result<ExecReturnValue, DispatchError>>; pub type ContractExecResult = ContractResult<Result<ExecReturnValue, DispatchError>>;
/// Result type of a `bare_instantiate` call. /// Result type of a `bare_instantiate` call.
pub type ContractInstantiateResult<AccountId, BlockNumber> = pub type ContractInstantiateResult<AccountId> =
ContractResult<Result<InstantiateReturnValue<AccountId, BlockNumber>, DispatchError>>; ContractResult<Result<InstantiateReturnValue<AccountId>, DispatchError>>;
/// Result type of a `get_storage` call. /// Result type of a `get_storage` call.
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>; pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
/// Result type of a `rent_projection` call.
pub type RentProjectionResult<BlockNumber> =
Result<RentProjection<BlockNumber>, ContractAccessError>;
/// The possible errors that can happen querying the storage of a contract. /// The possible errors that can happen querying the storage of a contract.
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] #[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
pub enum ContractAccessError { pub enum ContractAccessError {
/// The given address doesn't point to a contract. /// The given address doesn't point to a contract.
DoesntExist, DoesntExist,
/// The specified contract is a tombstone and thus cannot have any storage.
IsTombstone,
}
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
pub enum RentProjection<BlockNumber> {
/// Eviction is projected to happen at the specified block number.
EvictionAt(BlockNumber),
/// No eviction is scheduled.
///
/// E.g. Contract accumulated enough funds to offset the rent storage costs.
NoEviction,
} }
bitflags! { bitflags! {
@@ -134,19 +116,11 @@ impl ExecReturnValue {
#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] #[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
pub struct InstantiateReturnValue<AccountId, BlockNumber> { pub struct InstantiateReturnValue<AccountId> {
/// The output of the called constructor. /// The output of the called constructor.
pub result: ExecReturnValue, pub result: ExecReturnValue,
/// The account id of the new contract. /// The account id of the new contract.
pub account_id: AccountId, pub account_id: AccountId,
/// Information about when and if the new project will be evicted.
///
/// # Note
///
/// `None` if `bare_instantiate` was called with
/// `compute_projection` set to false. From the perspective of an RPC this means that
/// the runtime API did not request this value and this feature is therefore unsupported.
pub rent_projection: Option<RentProjection<BlockNumber>>,
} }
/// Reference to an existing code hash or a new wasm module. /// Reference to an existing code hash or a new wasm module.
@@ -1,43 +0,0 @@
(module
(import "seal0" "seal_rent_allowance" (func $seal_rent_allowance (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) reserved for $seal_rent_allowance output
;; [8, 16) length of the buffer
(data (i32.const 8) "\08")
;; [16, inf) zero initialized
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "call"))
(func (export "deploy")
;; fill the buffer with the rent allowance.
(call $seal_rent_allowance (i32.const 0) (i32.const 8))
;; assert len == 8
(call $assert
(i32.eq
(i32.load (i32.const 8))
(i32.const 8)
)
)
;; assert that contents of the buffer is equal to <BalanceOf<T>>::max_value().
(call $assert
(i64.eq
(i64.load (i32.const 0))
(i64.const 0xFFFFFFFFFFFFFFFF)
)
)
)
)
@@ -145,7 +145,7 @@
;; Calling the destination address with non-empty input data should now work since the ;; Calling the destination address with non-empty input data should now work since the
;; contract has been removed. Also transfer a balance to the address so we can ensure this ;; contract has been removed. Also transfer a balance to the address so we can ensure this
;; does not keep the contract alive. ;; does not hinder the contract from being removed.
(call $assert (call $assert
(i32.eq (i32.eq
(call $seal_transfer (call $seal_transfer
@@ -1,72 +0,0 @@
(module
(import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal1" "seal_restore_to"
(func $seal_restore_to
(param i32 i32 i32 i32 i32)
)
)
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "call")
;; copy code hash to contract memory
(call $seal_input (i32.const 308) (i32.const 304))
(call $assert
(i32.eq
(i32.load (i32.const 304))
(i32.const 64)
)
)
(call $seal_restore_to
;; Pointer to the encoded dest buffer.
(i32.const 340)
;; Pointer to the encoded code hash buffer
(i32.const 308)
;; Pointer to the encoded rent_allowance buffer
(i32.const 296)
;; Pointer and number of items in the delta buffer.
;; This buffer specifies multiple keys for removal before restoration.
(i32.const 100)
(i32.const 1)
)
)
(func (export "deploy")
;; Data to restore
(call $seal_set_storage
(i32.const 0)
(i32.const 0)
(i32.const 4)
)
;; ACL
(call $seal_set_storage
(i32.const 100)
(i32.const 0)
(i32.const 4)
)
)
;; Data to restore
(data (i32.const 0) "\28")
;; Buffer that has ACL storage keys.
(data (i32.const 100) "\01")
;; [296, 304) Rent allowance
(data (i32.const 296) "\32\00\00\00\00\00\00\00")
;; [304, 308) Size of the buffer that holds code_hash + addr
(data (i32.const 304) "\40")
;; [308, 340) code hash of bob (copied by seal_input)
;; [340, 372) addr of bob (copied by seal_input)
)
@@ -1,105 +0,0 @@
(module
(import "seal0" "seal_transfer" (func $seal_transfer (param i32 i32 i32 i32) (result i32)))
(import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
(import "seal0" "seal_clear_storage" (func $seal_clear_storage (param i32)))
(import "seal0" "seal_set_rent_allowance" (func $seal_set_rent_allowance (param i32 i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; insert a value of 4 bytes into storage
(func $call_0
(call $seal_set_storage
(i32.const 1)
(i32.const 0)
(i32.const 4)
)
)
;; remove the value inserted by call_1
(func $call_1
(call $seal_clear_storage
(i32.const 1)
)
)
;; transfer 50 to CHARLIE
(func $call_2
(call $assert
(i32.eq
(call $seal_transfer (i32.const 136) (i32.const 32) (i32.const 100) (i32.const 8))
(i32.const 0)
)
)
)
;; do nothing
(func $call_else)
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
;; Dispatch the call according to input size
(func (export "call")
(local $input_size i32)
;; 4 byte i32 for br_table followed by 32 byte destination for transfer
(i32.store (i32.const 128) (i32.const 36))
(call $seal_input (i32.const 132) (i32.const 128))
(set_local $input_size
(i32.load (i32.const 132))
)
(block $IF_ELSE
(block $IF_2
(block $IF_1
(block $IF_0
(br_table $IF_0 $IF_1 $IF_2 $IF_ELSE
(get_local $input_size)
)
(unreachable)
)
(call $call_0)
return
)
(call $call_1)
return
)
(call $call_2)
return
)
(call $call_else)
)
;; Set into storage a 4 bytes value
;; Set call set_rent_allowance with input
(func (export "deploy")
(call $seal_set_storage
(i32.const 0)
(i32.const 0)
(i32.const 4)
)
(i32.store (i32.const 128) (i32.const 64))
(call $seal_input
(i32.const 132)
(i32.const 128)
)
(call $seal_set_rent_allowance
(i32.const 132)
(i32.load (i32.const 128))
)
)
;; Encoding of 10 in balance
(data (i32.const 0) "\28")
;; encoding of 50 balance
(data (i32.const 100) "\32")
;; [128, 132) size of seal input buffer
;; [132, inf) output buffer for seal input
)
@@ -25,7 +25,7 @@
use codec::Codec; use codec::Codec;
use pallet_contracts_primitives::{ use pallet_contracts_primitives::{
Code, ContractExecResult, ContractInstantiateResult, GetStorageResult, RentProjectionResult, Code, ContractExecResult, ContractInstantiateResult, GetStorageResult,
}; };
use sp_std::vec::Vec; use sp_std::vec::Vec;
@@ -58,25 +58,16 @@ sp_api::decl_runtime_apis! {
code: Code<Hash>, code: Code<Hash>,
data: Vec<u8>, data: Vec<u8>,
salt: Vec<u8>, salt: Vec<u8>,
) -> ContractInstantiateResult<AccountId, BlockNumber>; ) -> ContractInstantiateResult<AccountId>;
/// Query a given storage key in a given contract. /// Query a given storage key in a given contract.
/// ///
/// Returns `Ok(Some(Vec<u8>))` if the storage value exists under the given key in the /// Returns `Ok(Some(Vec<u8>))` if the storage value exists under the given key in the
/// specified account and `Ok(None)` if it doesn't. If the account specified by the address /// specified account and `Ok(None)` if it doesn't. If the account specified by the address
/// doesn't exist, or doesn't have a contract or if the contract is a tombstone, then `Err` /// doesn't exist, or doesn't have a contract then `Err` is returned.
/// is returned.
fn get_storage( fn get_storage(
address: AccountId, address: AccountId,
key: [u8; 32], key: [u8; 32],
) -> GetStorageResult; ) -> GetStorageResult;
/// Returns the projected time a given contract will be able to sustain paying its rent.
///
/// The returned projection is relevant 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>;
} }
} }
+5 -50
View File
@@ -22,9 +22,7 @@ use std::sync::Arc;
use codec::Codec; use codec::Codec;
use jsonrpc_core::{Error, ErrorCode, Result}; use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc; use jsonrpc_derive::rpc;
use pallet_contracts_primitives::{ use pallet_contracts_primitives::{Code, ContractExecResult, ContractInstantiateResult};
Code, ContractExecResult, ContractInstantiateResult, RentProjection,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sp_api::ProvideRuntimeApi; use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend; use sp_blockchain::HeaderBackend;
@@ -40,7 +38,6 @@ pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;
const RUNTIME_ERROR: i64 = 1; const RUNTIME_ERROR: i64 = 1;
const CONTRACT_DOESNT_EXIST: i64 = 2; const CONTRACT_DOESNT_EXIST: i64 = 2;
const CONTRACT_IS_A_TOMBSTONE: i64 = 3;
pub type Weight = u64; pub type Weight = u64;
@@ -69,11 +66,6 @@ impl From<ContractAccessError> for Error {
message: "The specified contract doesn't exist.".into(), message: "The specified contract doesn't exist.".into(),
data: None, data: None,
}, },
IsTombstone => Error {
code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE),
message: "The contract is a tombstone and doesn't have any storage.".into(),
data: None,
},
} }
} }
} }
@@ -130,7 +122,7 @@ pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash> {
&self, &self,
instantiate_request: InstantiateRequest<AccountId, Hash>, instantiate_request: InstantiateRequest<AccountId, Hash>,
at: Option<BlockHash>, at: Option<BlockHash>,
) -> Result<ContractInstantiateResult<AccountId, BlockNumber>>; ) -> Result<ContractInstantiateResult<AccountId>>;
/// Returns the value under a specified storage `key` in a contract given by `address` param, /// Returns the value under a specified storage `key` in a contract given by `address` param,
/// or `None` if it is not set. /// or `None` if it is not set.
@@ -141,19 +133,6 @@ pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash> {
key: H256, key: H256,
at: Option<BlockHash>, at: Option<BlockHash>,
) -> Result<Option<Bytes>>; ) -> Result<Option<Bytes>>;
/// Returns the projected time a given contract will be able to sustain paying its rent.
///
/// The returned projection is relevant 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. /// An implementation of contract specific RPC methods.
@@ -217,8 +196,7 @@ where
&self, &self,
instantiate_request: InstantiateRequest<AccountId, Hash>, instantiate_request: InstantiateRequest<AccountId, Hash>,
at: Option<<Block as BlockT>::Hash>, at: Option<<Block as BlockT>::Hash>,
) -> Result<ContractInstantiateResult<AccountId, <<Block as BlockT>::Header as HeaderT>::Number>> ) -> Result<ContractInstantiateResult<AccountId>> {
{
let api = self.client.runtime_api(); let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(|| let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block. // If the block hash is not supplied assume the best block.
@@ -257,27 +235,6 @@ where
Ok(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(runtime_error_into_rpc_err)?
.map_err(ContractAccessError)?;
Ok(match result {
RentProjection::NoEviction => None,
RentProjection::EvictionAt(block_num) => Some(block_num),
})
}
} }
/// Converts a runtime trap into an RPC error. /// Converts a runtime trap into an RPC error.
@@ -404,8 +361,7 @@ mod tests {
#[test] #[test]
fn instantiate_result_should_serialize_deserialize_properly() { fn instantiate_result_should_serialize_deserialize_properly() {
fn test(expected: &str) { fn test(expected: &str) {
let res: ContractInstantiateResult<String, u64> = let res: ContractInstantiateResult<String> = serde_json::from_str(expected).unwrap();
serde_json::from_str(expected).unwrap();
let actual = serde_json::to_string(&res).unwrap(); let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, trim(expected).as_str()); assert_eq!(actual, trim(expected).as_str());
} }
@@ -420,8 +376,7 @@ mod tests {
"flags": 5, "flags": 5,
"data": "0x1234" "data": "0x1234"
}, },
"accountId": "5CiPP", "accountId": "5CiPP"
"rentProjection": null
} }
} }
}"#, }"#,
File diff suppressed because it is too large Load Diff
+47 -423
View File
@@ -16,11 +16,8 @@
// limitations under the License. // limitations under the License.
use crate::{ use crate::{
gas::GasMeter, gas::GasMeter, storage::Storage, AccountCounter, BalanceOf, CodeHash, Config, ContractInfo,
rent::{Rent, RentStatus}, ContractInfoOf, Error, Event, Pallet as Contracts, Schedule,
storage::Storage,
AccountCounter, AliveContractInfo, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf,
Error, Event, Pallet as Contracts, Schedule,
}; };
use frame_support::{ use frame_support::{
dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable}, dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable},
@@ -28,18 +25,19 @@ use frame_support::{
storage::{with_transaction, TransactionOutcome}, storage::{with_transaction, TransactionOutcome},
traits::{Contains, Currency, ExistenceRequirement, Get, OriginTrait, Randomness, Time}, traits::{Contains, Currency, ExistenceRequirement, Get, OriginTrait, Randomness, Time},
weights::Weight, weights::Weight,
DefaultNoBound,
}; };
use frame_system::RawOrigin; use frame_system::RawOrigin;
use pallet_contracts_primitives::ExecReturnValue; use pallet_contracts_primitives::ExecReturnValue;
use smallvec::{Array, SmallVec}; use smallvec::{Array, SmallVec};
use sp_core::crypto::UncheckedFrom; use sp_core::crypto::UncheckedFrom;
use sp_runtime::{ use sp_runtime::traits::{Convert, Saturating};
traits::{Convert, Saturating},
Perbill,
};
use sp_std::{marker::PhantomData, mem, prelude::*}; use sp_std::{marker::PhantomData, mem, prelude::*};
/// When fields are added to the [`ContractInfo`] that can change during execution this
/// variable needs to be set to true. This will also force changes to the
/// `in_memory_changes_not_discarded` test.
const CONTRACT_INFO_CAN_CHANGE: bool = false;
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId; pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment; pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
pub type SeedOf<T> = <T as frame_system::Config>::Hash; pub type SeedOf<T> = <T as frame_system::Config>::Hash;
@@ -81,67 +79,6 @@ impl<T: Into<DispatchError>> From<T> for ExecError {
} }
} }
/// Information needed for rent calculations that can be requested by a contract.
#[derive(codec::Encode, DefaultNoBound)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RentParams<T: Config> {
/// The total balance of the contract. Includes the balance transferred from the caller.
total_balance: BalanceOf<T>,
/// The free balance of the contract. Includes the balance transferred from the caller.
free_balance: BalanceOf<T>,
/// See crate [`Contracts::subsistence_threshold()`].
subsistence_threshold: BalanceOf<T>,
/// See crate [`Config::DepositPerContract`].
deposit_per_contract: BalanceOf<T>,
/// See crate [`Config::DepositPerStorageByte`].
deposit_per_storage_byte: BalanceOf<T>,
/// See crate [`Config::DepositPerStorageItem`].
deposit_per_storage_item: BalanceOf<T>,
/// See crate [`Ext::rent_allowance()`].
rent_allowance: BalanceOf<T>,
/// See crate [`Config::RentFraction`].
rent_fraction: Perbill,
/// See crate [`AliveContractInfo::storage_size`].
storage_size: u32,
/// See crate [`Executable::aggregate_code_len()`].
code_size: u32,
/// See crate [`Executable::refcount()`].
code_refcount: u32,
/// Reserved for backwards compatible changes to this data structure.
_reserved: Option<()>,
}
impl<T> RentParams<T>
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
/// Derive new `RentParams` from the passed in data.
///
/// `value` is added to the current free and total balance of the contracts' account.
fn new<E: Executable<T>>(
account_id: &T::AccountId,
value: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
executable: &E,
) -> Self {
Self {
total_balance: T::Currency::total_balance(account_id).saturating_add(*value),
free_balance: T::Currency::free_balance(account_id).saturating_add(*value),
subsistence_threshold: <Contracts<T>>::subsistence_threshold(),
deposit_per_contract: T::DepositPerContract::get(),
deposit_per_storage_byte: T::DepositPerStorageByte::get(),
deposit_per_storage_item: T::DepositPerStorageItem::get(),
rent_allowance: contract.rent_allowance,
rent_fraction: T::RentFraction::get(),
storage_size: contract.storage_size,
code_size: executable.aggregate_code_len(),
code_refcount: executable.refcount(),
_reserved: None,
}
}
}
/// An interface that provides access to the external environment in which the /// An interface that provides access to the external environment in which the
/// smart-contract is executed. /// smart-contract is executed.
/// ///
@@ -197,25 +134,6 @@ pub trait Ext: sealing::Sealed {
/// call stack. /// call stack.
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError>; fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError>;
/// Restores the given destination contract sacrificing the current one.
///
/// Since this function removes the self contract eagerly, if succeeded, no further actions
/// should be performed on this `Ext` instance.
///
/// This function will fail if the same contract is present
/// on the contract call stack.
///
/// # Return Value
///
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), DispatchError>;
/// Transfer some amount of funds into the specified account. /// Transfer some amount of funds into the specified account.
fn transfer(&mut self, to: &AccountIdOf<Self::T>, value: BalanceOf<Self::T>) -> DispatchResult; fn transfer(&mut self, to: &AccountIdOf<Self::T>, value: BalanceOf<Self::T>) -> DispatchResult;
@@ -249,8 +167,8 @@ pub trait Ext: sealing::Sealed {
/// Returns the minimum balance that is required for creating an account. /// Returns the minimum balance that is required for creating an account.
fn minimum_balance(&self) -> BalanceOf<Self::T>; fn minimum_balance(&self) -> BalanceOf<Self::T>;
/// Returns the deposit required to create a tombstone upon contract eviction. /// Returns the deposit required to instantiate a contract.
fn tombstone_deposit(&self) -> BalanceOf<Self::T>; fn contract_deposit(&self) -> BalanceOf<Self::T>;
/// Returns a random number for the current block with the given subject. /// Returns a random number for the current block with the given subject.
fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>); fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>);
@@ -260,12 +178,6 @@ pub trait Ext: sealing::Sealed {
/// There should not be any duplicates in `topics`. /// There should not be any duplicates in `topics`.
fn deposit_event(&mut self, topics: Vec<TopicOf<Self::T>>, data: Vec<u8>); fn deposit_event(&mut self, topics: Vec<TopicOf<Self::T>>, data: Vec<u8>);
/// Set rent allowance of the contract
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<Self::T>);
/// Rent allowance of the contract
fn rent_allowance(&mut self) -> BalanceOf<Self::T>;
/// Returns the current block number. /// Returns the current block number.
fn block_number(&self) -> BlockNumberOf<Self::T>; fn block_number(&self) -> BlockNumberOf<Self::T>;
@@ -278,12 +190,6 @@ pub trait Ext: sealing::Sealed {
/// Get a reference to the schedule used by the current call. /// Get a reference to the schedule used by the current call.
fn schedule(&self) -> &Schedule<Self::T>; fn schedule(&self) -> &Schedule<Self::T>;
/// Information needed for rent calculations.
fn rent_params(&self) -> &RentParams<Self::T>;
/// Information about the required deposit and resulting rent.
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T>;
/// Get a mutable reference to the nested gas meter. /// Get a mutable reference to the nested gas meter.
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>; fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
@@ -336,9 +242,6 @@ pub trait Executable<T: Config>: Sized {
/// Does not charge from the gas meter. Do not call in contexts where this is important. /// Does not charge from the gas meter. Do not call in contexts where this is important.
fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError>; fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError>;
/// Decrements the refcount by one and deletes the code if it drops to zero.
fn drop_from_storage(self);
/// Increment the refcount by one. Fails if the code does not exist on-chain. /// Increment the refcount by one. Fails if the code does not exist on-chain.
/// ///
/// Returns the size of the original code. /// Returns the size of the original code.
@@ -387,23 +290,6 @@ pub trait Executable<T: Config>: Sized {
// The number of contracts using this executable. // The number of contracts using this executable.
fn refcount(&self) -> u32; fn refcount(&self) -> u32;
/// The storage that is occupied by the instrumented executable and its pristine source.
///
/// The returned size is already divided by the number of users who share the code.
/// This is essentially `aggregate_code_len() / refcount()`.
///
/// # Note
///
/// This works with the current in-memory value of refcount. When calling any contract
/// without refetching this from storage the result can be inaccurate as it might be
/// working with a stale value. Usually this inaccuracy is tolerable.
fn occupied_storage(&self) -> u32 {
// We disregard the size of the struct itself as the size is completely
// dominated by the code size.
let len = self.aggregate_code_len();
len.checked_div(self.refcount()).unwrap_or(len)
}
} }
/// The complete call stack of a contract execution. /// The complete call stack of a contract execution.
@@ -461,8 +347,6 @@ pub struct Frame<T: Config> {
contract_info: CachedContract<T>, contract_info: CachedContract<T>,
/// The amount of balance transferred by the caller as part of the call. /// The amount of balance transferred by the caller as part of the call.
value_transferred: BalanceOf<T>, value_transferred: BalanceOf<T>,
/// Snapshotted rent information that can be copied to the contract if requested.
rent_params: RentParams<T>,
/// Determines whether this is a call or instantiate frame. /// Determines whether this is a call or instantiate frame.
entry_point: ExportedFunction, entry_point: ExportedFunction,
/// The gas meter capped to the supplied gas limit. /// The gas meter capped to the supplied gas limit.
@@ -479,7 +363,7 @@ enum FrameArgs<'a, T: Config, E> {
/// The account id of the contract that is to be called. /// The account id of the contract that is to be called.
dest: T::AccountId, dest: T::AccountId,
/// If `None` the contract info needs to be reloaded from storage. /// If `None` the contract info needs to be reloaded from storage.
cached_info: Option<AliveContractInfo<T>>, cached_info: Option<ContractInfo<T>>,
}, },
Instantiate { Instantiate {
/// The contract or signed origin which instantiates the new contract. /// The contract or signed origin which instantiates the new contract.
@@ -496,12 +380,12 @@ enum FrameArgs<'a, T: Config, E> {
/// Describes the different states of a contract as contained in a `Frame`. /// Describes the different states of a contract as contained in a `Frame`.
enum CachedContract<T: Config> { enum CachedContract<T: Config> {
/// The cached contract is up to date with the in-storage value. /// The cached contract is up to date with the in-storage value.
Cached(AliveContractInfo<T>), Cached(ContractInfo<T>),
/// A recursive call into the same contract did write to the contract info. /// A recursive call into the same contract did write to the contract info.
/// ///
/// In this case the cached contract is stale and needs to be reloaded from storage. /// In this case the cached contract is stale and needs to be reloaded from storage.
Invalidated, Invalidated,
/// The current contract executed `terminate` or `restore_to` and removed the contract. /// The current contract executed `terminate` and removed the contract.
/// ///
/// In this case a reload is neither allowed nor possible. Please note that recursive /// In this case a reload is neither allowed nor possible. Please note that recursive
/// calls cannot remove a contract as this is checked and denied. /// calls cannot remove a contract as this is checked and denied.
@@ -510,13 +394,8 @@ enum CachedContract<T: Config> {
impl<T: Config> Frame<T> { impl<T: Config> Frame<T> {
/// Return the `contract_info` of the current contract. /// Return the `contract_info` of the current contract.
fn contract_info(&mut self) -> &mut AliveContractInfo<T> { fn contract_info(&mut self) -> &mut ContractInfo<T> {
self.contract_info.as_alive(&self.account_id) self.contract_info.get(&self.account_id)
}
/// Invalidate and return the `contract_info` of the current contract.
fn invalidate(&mut self) -> AliveContractInfo<T> {
self.contract_info.invalidate(&self.account_id)
} }
/// Terminate and return the `contract_info` of the current contract. /// Terminate and return the `contract_info` of the current contract.
@@ -525,7 +404,7 @@ impl<T: Config> Frame<T> {
/// ///
/// Under no circumstances the contract is allowed to access the `contract_info` after /// Under no circumstances the contract is allowed to access the `contract_info` after
/// a call to this function. This would constitute a programming error in the exec module. /// a call to this function. This would constitute a programming error in the exec module.
fn terminate(&mut self) -> AliveContractInfo<T> { fn terminate(&mut self) -> ContractInfo<T> {
self.contract_info.terminate(&self.account_id) self.contract_info.terminate(&self.account_id)
} }
} }
@@ -540,7 +419,7 @@ macro_rules! get_cached_or_panic_after_load {
} else { } else {
panic!( panic!(
"It is impossible to remove a contract that is on the call stack;\ "It is impossible to remove a contract that is on the call stack;\
See implementations of terminate and restore_to;\ See implementations of terminate;\
Therefore fetching a contract will never fail while using an account id Therefore fetching a contract will never fail while using an account id
that is currently active on the call stack;\ that is currently active on the call stack;\
qed" qed"
@@ -553,28 +432,21 @@ impl<T: Config> CachedContract<T> {
/// Load the `contract_info` from storage if necessary. /// Load the `contract_info` from storage if necessary.
fn load(&mut self, account_id: &T::AccountId) { fn load(&mut self, account_id: &T::AccountId) {
if let CachedContract::Invalidated = self { if let CachedContract::Invalidated = self {
let contract = let contract = <ContractInfoOf<T>>::get(&account_id);
<ContractInfoOf<T>>::get(&account_id).and_then(|contract| contract.get_alive());
if let Some(contract) = contract { if let Some(contract) = contract {
*self = CachedContract::Cached(contract); *self = CachedContract::Cached(contract);
} }
} }
} }
/// Return the cached contract_info as alive contract info. /// Return the cached contract_info.
fn as_alive(&mut self, account_id: &T::AccountId) -> &mut AliveContractInfo<T> { fn get(&mut self, account_id: &T::AccountId) -> &mut ContractInfo<T> {
self.load(account_id); self.load(account_id);
get_cached_or_panic_after_load!(self) get_cached_or_panic_after_load!(self)
} }
/// Invalidate and return the contract info.
fn invalidate(&mut self, account_id: &T::AccountId) -> AliveContractInfo<T> {
self.load(account_id);
get_cached_or_panic_after_load!(mem::replace(self, Self::Invalidated))
}
/// Terminate and return the contract info. /// Terminate and return the contract info.
fn terminate(&mut self, account_id: &T::AccountId) -> AliveContractInfo<T> { fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo<T> {
self.load(account_id); self.load(account_id);
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated)) get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated))
} }
@@ -695,23 +567,11 @@ where
let contract = if let Some(contract) = cached_info { let contract = if let Some(contract) = cached_info {
contract contract
} else { } else {
<ContractInfoOf<T>>::get(&dest) <ContractInfoOf<T>>::get(&dest).ok_or(<Error<T>>::ContractNotFound)?
.ok_or(<Error<T>>::ContractNotFound.into())
.and_then(|contract| {
contract.get_alive().ok_or(<Error<T>>::ContractIsTombstone)
})?
}; };
let executable = E::from_storage(contract.code_hash, schedule, gas_meter)?; let executable = E::from_storage(contract.code_hash, schedule, gas_meter)?;
// This charges the rent and denies access to a contract that is in need of
// eviction by returning `None`. We cannot evict eagerly here because those
// changes would be rolled back in case this contract is called by another
// contract.
// See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324
let contract =
Rent::<T, E>::charge(&dest, contract, executable.occupied_storage())?
.ok_or(Error::<T>::RentNotPaid)?;
(dest, contract, executable, ExportedFunction::Call) (dest, contract, executable, ExportedFunction::Call)
}, },
FrameArgs::Instantiate { sender, trie_seed, executable, salt } => { FrameArgs::Instantiate { sender, trie_seed, executable, salt } => {
@@ -728,12 +588,6 @@ where
}; };
let frame = Frame { let frame = Frame {
rent_params: RentParams::new(
&account_id,
&value_transferred,
&contract_info,
&executable,
),
value_transferred, value_transferred,
contract_info: CachedContract::Cached(contract_info), contract_info: CachedContract::Cached(contract_info),
account_id, account_id,
@@ -756,18 +610,17 @@ where
return Err(Error::<T>::MaxCallDepthReached.into()) return Err(Error::<T>::MaxCallDepthReached.into())
} }
// We need to make sure that changes made to the contract info are not discarded. if CONTRACT_INFO_CAN_CHANGE {
// See the `in_memory_changes_not_discarded` test for more information. // We need to make sure that changes made to the contract info are not discarded.
// We do not store on instantiate because we do not allow to call into a contract // See the `in_memory_changes_not_discarded` test for more information.
// from its own constructor. // We do not store on instantiate because we do not allow to call into a contract
let frame = self.top_frame(); // from its own constructor.
if let (CachedContract::Cached(contract), ExportedFunction::Call) = let frame = self.top_frame();
(&frame.contract_info, frame.entry_point) if let (CachedContract::Cached(contract), ExportedFunction::Call) =
{ (&frame.contract_info, frame.entry_point)
<ContractInfoOf<T>>::insert( {
frame.account_id.clone(), <ContractInfoOf<T>>::insert(frame.account_id.clone(), contract.clone());
ContractInfo::Alive(contract.clone()), }
);
} }
let nested_meter = let nested_meter =
@@ -784,12 +637,6 @@ where
fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<ExecReturnValue, ExecError> { fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<ExecReturnValue, ExecError> {
let entry_point = self.top_frame().entry_point; let entry_point = self.top_frame().entry_point;
let do_transaction = || { let do_transaction = || {
// Cache the value before calling into the constructor because that
// consumes the value. If the constructor creates additional contracts using
// the same code hash we still charge the "1 block rent" as if they weren't
// spawned. This is OK as overcharging is always safe.
let occupied_storage = executable.occupied_storage();
// Every call or instantiate also optionally transferres balance. // Every call or instantiate also optionally transferres balance.
self.initial_transfer()?; self.initial_transfer()?;
@@ -808,16 +655,6 @@ where
return Err(Error::<T>::TerminatedInConstructor.into()) return Err(Error::<T>::TerminatedInConstructor.into())
} }
// Collect the rent for the first block to prevent the creation of very large
// contracts that never intended to pay for even one block.
// This also makes sure that it is above the subsistence threshold
// in order to keep up the guarantuee that we always leave a tombstone behind
// with the exception of a contract that called `seal_terminate`.
let contract =
Rent::<T, E>::charge(&account_id, frame.invalidate(), occupied_storage)?
.ok_or(Error::<T>::NewContractNotFunded)?;
frame.contract_info = CachedContract::Cached(contract);
// Deposit an instantiation event. // Deposit an instantiation event.
deposit_event::<T>(vec![], Event::Instantiated(self.caller().clone(), account_id)); deposit_event::<T>(vec![], Event::Instantiated(self.caller().clone(), account_id));
} }
@@ -877,7 +714,7 @@ where
// because that case is already handled by the optimization above. Only the first // because that case is already handled by the optimization above. Only the first
// cache needs to be invalidated because that one will invalidate the next cache // cache needs to be invalidated because that one will invalidate the next cache
// when it is popped from the stack. // when it is popped from the stack.
<ContractInfoOf<T>>::insert(account_id, ContractInfo::Alive(contract)); <ContractInfoOf<T>>::insert(account_id, contract);
if let Some(c) = self.frames_mut().skip(1).find(|f| f.account_id == *account_id) { if let Some(c) = self.frames_mut().skip(1).find(|f| f.account_id == *account_id) {
c.contract_info = CachedContract::Invalidated; c.contract_info = CachedContract::Invalidated;
} }
@@ -897,10 +734,7 @@ where
return return
} }
if let CachedContract::Cached(contract) = &self.first_frame.contract_info { if let CachedContract::Cached(contract) = &self.first_frame.contract_info {
<ContractInfoOf<T>>::insert( <ContractInfoOf<T>>::insert(&self.first_frame.account_id, contract.clone());
&self.first_frame.account_id,
ContractInfo::Alive(contract.clone()),
);
} }
if let Some(counter) = self.account_counter { if let Some(counter) = self.account_counter {
<AccountCounter<T>>::set(counter); <AccountCounter<T>>::set(counter);
@@ -1111,38 +945,6 @@ where
Ok(()) Ok(())
} }
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), DispatchError> {
if self.is_recursive() {
return Err(Error::<T>::TerminatedWhileReentrant.into())
}
let frame = self.top_frame_mut();
let origin_contract = frame.contract_info().clone();
let account_id = frame.account_id.clone();
let result = Rent::<T, E>::restore_to(
&account_id,
origin_contract,
dest.clone(),
code_hash.clone(),
rent_allowance,
delta,
&mut frame.nested_meter,
);
if let Ok(_) = result {
deposit_event::<Self::T>(
vec![],
Event::Restored(account_id, dest, code_hash, rent_allowance),
);
frame.terminate();
}
result
}
fn transfer(&mut self, to: &T::AccountId, value: BalanceOf<T>) -> DispatchResult { fn transfer(&mut self, to: &T::AccountId, value: BalanceOf<T>) -> DispatchResult {
Self::transfer(true, false, &self.top_frame().account_id, to, value) Self::transfer(true, false, &self.top_frame().account_id, to, value)
} }
@@ -1152,9 +954,8 @@ where
} }
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult { fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
let block_number = self.block_number;
let frame = self.top_frame_mut(); let frame = self.top_frame_mut();
Storage::<T>::write(block_number, frame.contract_info(), &key, value) Storage::<T>::write(frame.contract_info(), &key, value)
} }
fn address(&self) -> &T::AccountId { fn address(&self) -> &T::AccountId {
@@ -1185,8 +986,8 @@ where
T::Currency::minimum_balance() T::Currency::minimum_balance()
} }
fn tombstone_deposit(&self) -> BalanceOf<T> { fn contract_deposit(&self) -> BalanceOf<T> {
T::TombstoneDeposit::get() T::ContractDeposit::get()
} }
fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) { fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) {
@@ -1196,14 +997,6 @@ where
); );
} }
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<T>) {
self.top_frame_mut().contract_info().rent_allowance = rent_allowance;
}
fn rent_allowance(&mut self) -> BalanceOf<T> {
self.top_frame_mut().contract_info().rent_allowance
}
fn block_number(&self) -> T::BlockNumber { fn block_number(&self) -> T::BlockNumber {
self.block_number self.block_number
} }
@@ -1220,24 +1013,6 @@ where
&self.schedule &self.schedule
} }
fn rent_params(&self) -> &RentParams<Self::T> {
&self.top_frame().rent_params
}
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T> {
let frame = self.top_frame_mut();
let balance = T::Currency::free_balance(&frame.account_id);
let code_size = frame.rent_params.code_size;
let refcount = frame.rent_params.code_refcount;
<Rent<T, E>>::rent_status(
&balance,
&frame.contract_info(),
code_size,
refcount,
at_refcount,
)
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> { fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_meter &mut self.top_frame_mut().nested_meter
} }
@@ -1304,7 +1079,7 @@ mod tests {
use frame_support::{assert_err, assert_ok}; use frame_support::{assert_err, assert_ok};
use frame_system::{EventRecord, Phase}; use frame_system::{EventRecord, Phase};
use pallet_contracts_primitives::ReturnFlags; use pallet_contracts_primitives::ReturnFlags;
use pretty_assertions::{assert_eq, assert_ne}; use pretty_assertions::assert_eq;
use sp_core::Bytes; use sp_core::Bytes;
use sp_runtime::{ use sp_runtime::{
traits::{BadOrigin, Hash}, traits::{BadOrigin, Hash},
@@ -1400,12 +1175,6 @@ mod tests {
} }
}); });
} }
fn refcount(code_hash: &CodeHash<Test>) -> u32 {
LOADER.with(|loader| {
loader.borrow().map.get(code_hash).expect("code_hash does not exist").refcount()
})
}
} }
impl Executable<Test> for MockExecutable { impl Executable<Test> for MockExecutable {
@@ -1428,10 +1197,6 @@ mod tests {
}) })
} }
fn drop_from_storage(self) {
MockLoader::decrement_refcount(self.code_hash);
}
fn add_user( fn add_user(
code_hash: CodeHash<Test>, code_hash: CodeHash<Test>,
_: &mut GasMeter<Test>, _: &mut GasMeter<Test>,
@@ -1546,7 +1311,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| { ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get(); let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch); place_contract(&dest, return_ch);
set_balance(&origin, 100); set_balance(&origin, 100);
let balance = get_balance(&dest); let balance = get_balance(&dest);
@@ -1563,9 +1328,7 @@ mod tests {
assert!(!output.is_success()); assert!(!output.is_success());
assert_eq!(get_balance(&origin), 100); assert_eq!(get_balance(&origin), 100);
assert_eq!(get_balance(&dest), balance);
// the rent is still charged
assert!(get_balance(&dest) < balance);
}); });
} }
@@ -2066,147 +1829,12 @@ mod tests {
}); });
} }
#[test]
fn rent_allowance() {
let rent_allowance_ch = MockLoader::insert(Constructor, |ctx, _| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let allowance = subsistence * 3;
assert_eq!(ctx.ext.rent_allowance(), <BalanceOf<Test>>::max_value());
ctx.ext.set_rent_allowance(allowance);
assert_eq!(ctx.ext.rent_allowance(), allowance);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(rent_allowance_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, subsistence * 10);
let result = MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&schedule,
subsistence * 5,
vec![],
&[],
None,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn rent_params_works() {
let code_hash = MockLoader::insert(Call, |ctx, executable| {
let address = ctx.ext.address();
let contract =
<ContractInfoOf<Test>>::get(address).and_then(|c| c.get_alive()).unwrap();
assert_eq!(ctx.ext.rent_params(), &RentParams::new(address, &0, &contract, executable));
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, 0, vec![], None).unwrap();
});
}
#[test]
fn rent_params_snapshotted() {
let code_hash = MockLoader::insert(Call, |ctx, executable| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let address = ctx.ext.address();
let contract =
<ContractInfoOf<Test>>::get(address).and_then(|c| c.get_alive()).unwrap();
let rent_params = RentParams::new(address, &0, &contract, executable);
// Changing the allowance during the call: rent params stay unchanged.
let allowance = 42;
assert_ne!(allowance, rent_params.rent_allowance);
ctx.ext.set_rent_allowance(allowance);
assert_eq!(ctx.ext.rent_params(), &rent_params);
// Creating another instance from the same code_hash increases the refcount.
// This is also not reflected in the rent params.
assert_eq!(MockLoader::refcount(&executable.code_hash), 1);
ctx.ext
.instantiate(0, executable.code_hash, subsistence * 25, vec![], &[])
.unwrap();
assert_eq!(MockLoader::refcount(&executable.code_hash), 2);
assert_eq!(ctx.ext.rent_params(), &rent_params);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 100);
place_contract(&BOB, code_hash);
MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&schedule,
subsistence * 50,
vec![],
None,
)
.unwrap();
});
}
#[test]
fn rent_status_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.rent_status(0),
RentStatus {
max_deposit: 80000,
current_deposit: 80000,
custom_refcount_deposit: None,
max_rent: 32,
current_rent: 32,
custom_refcount_rent: None,
_reserved: None,
}
);
assert_eq!(
ctx.ext.rent_status(1),
RentStatus {
max_deposit: 80000,
current_deposit: 80000,
custom_refcount_deposit: Some(80000),
max_rent: 32,
current_rent: 32,
custom_refcount_rent: Some(32),
_reserved: None,
}
);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, 0, vec![], None).unwrap();
});
}
#[test] #[test]
fn in_memory_changes_not_discarded() { fn in_memory_changes_not_discarded() {
// Remove this assert and fill out the "DO" stubs once fields are added to the
// contract info that can be modified during exection.
assert!(!CONTRACT_INFO_CAN_CHANGE);
// Call stack: BOB -> CHARLIE (trap) -> BOB' (success) // Call stack: BOB -> CHARLIE (trap) -> BOB' (success)
// This tests verfies some edge case of the contract info cache: // This tests verfies some edge case of the contract info cache:
// We change some value in our contract info before calling into a contract // We change some value in our contract info before calling into a contract
@@ -2217,13 +1845,9 @@ mod tests {
// are made before calling into CHARLIE are not discarded. // are made before calling into CHARLIE are not discarded.
let code_bob = MockLoader::insert(Call, |ctx, _| { let code_bob = MockLoader::insert(Call, |ctx, _| {
if ctx.input_data[0] == 0 { if ctx.input_data[0] == 0 {
let original_allowance = ctx.ext.rent_allowance(); // DO: modify medata (ContractInfo) of own contract through ctx.ext functions
let changed_allowance = <BalanceOf<Test>>::max_value() / 2;
assert_ne!(original_allowance, changed_allowance);
ctx.ext.set_rent_allowance(changed_allowance);
assert_eq!(ctx.ext.call(0, CHARLIE, 0, vec![], true), exec_trapped()); assert_eq!(ctx.ext.call(0, CHARLIE, 0, vec![], true), exec_trapped());
assert_eq!(ctx.ext.rent_allowance(), changed_allowance); // DO: check that the value is not discarded (query via ctx.ext)
assert_ne!(ctx.ext.rent_allowance(), original_allowance);
} }
exec_success() exec_success()
}); });
+30 -197
View File
@@ -70,7 +70,6 @@
//! * [`Pallet::instantiate`] - The same as `instantiate_with_code` but instead of uploading new //! * [`Pallet::instantiate`] - The same as `instantiate_with_code` but instead of uploading new
//! code an existing `code_hash` is supplied. //! code an existing `code_hash` is supplied.
//! * [`Pallet::call`] - Makes a call to an account, optionally transferring some balance. //! * [`Pallet::call`] - Makes a call to an account, optionally transferring some balance.
//! * [`Pallet::claim_surcharge`] - Evict a contract that cannot pay rent anymore.
//! //!
//! ## Usage //! ## Usage
//! //!
@@ -89,7 +88,6 @@ mod gas;
mod benchmarking; mod benchmarking;
mod exec; mod exec;
mod migration; mod migration;
mod rent;
mod schedule; mod schedule;
mod storage; mod storage;
mod wasm; mod wasm;
@@ -108,38 +106,31 @@ pub use crate::{
use crate::{ use crate::{
exec::{Executable, Stack as ExecStack}, exec::{Executable, Stack as ExecStack},
gas::GasMeter, gas::GasMeter,
rent::Rent, storage::{ContractInfo, DeletedContract, Storage},
storage::{AliveContractInfo, ContractInfo, DeletedContract, Storage, TombstoneContractInfo},
wasm::PrefabWasmModule, wasm::PrefabWasmModule,
weights::WeightInfo, weights::WeightInfo,
}; };
use frame_support::{ use frame_support::{
dispatch::Dispatchable, dispatch::Dispatchable,
traits::{Contains, Currency, Get, OnUnbalanced, Randomness, StorageVersion, Time}, traits::{Contains, Currency, Get, Randomness, StorageVersion, Time},
weights::{GetDispatchInfo, PostDispatchInfo, Weight, WithPostDispatchInfo}, weights::{GetDispatchInfo, PostDispatchInfo, Weight},
}; };
use frame_system::Pallet as System; use frame_system::Pallet as System;
use pallet_contracts_primitives::{ use pallet_contracts_primitives::{
Code, ContractAccessError, ContractExecResult, ContractInstantiateResult, GetStorageResult, Code, ContractAccessError, ContractExecResult, ContractInstantiateResult, GetStorageResult,
InstantiateReturnValue, RentProjectionResult, InstantiateReturnValue,
}; };
use sp_core::{crypto::UncheckedFrom, Bytes}; use sp_core::{crypto::UncheckedFrom, Bytes};
use sp_runtime::{ use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
traits::{Convert, Hash, Saturating, StaticLookup, Zero},
Perbill,
};
use sp_std::prelude::*; use sp_std::prelude::*;
type CodeHash<T> = <T as frame_system::Config>::Hash; type CodeHash<T> = <T as frame_system::Config>::Hash;
type TrieId = Vec<u8>; type TrieId = Vec<u8>;
type BalanceOf<T> = type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
/// The current storage version. /// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
@@ -177,8 +168,7 @@ pub mod pallet {
/// ///
/// The runtime **must** make sure that any allowed dispatchable makes sure that the /// The runtime **must** make sure that any allowed dispatchable makes sure that the
/// `total_balance` of the contract stays above [`Pallet::subsistence_threshold()`]. /// `total_balance` of the contract stays above [`Pallet::subsistence_threshold()`].
/// Otherwise contracts can clutter the storage with their tombstones without /// Otherwise users could clutter the storage with contracts.
/// deposting the correct amount of balance.
/// ///
/// # Stability /// # Stability
/// ///
@@ -195,9 +185,6 @@ pub mod pallet {
/// be exploited to drive the runtime into a panic. /// be exploited to drive the runtime into a panic.
type CallFilter: Contains<<Self as frame_system::Config>::Call>; type CallFilter: Contains<<Self as frame_system::Config>::Call>;
/// Handler for rent payments.
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// Used to answer contracts' queries regarding the current weight price. This is **not** /// Used to answer contracts' queries regarding the current weight price. This is **not**
/// used to calculate the actual fee and is only for informational purposes. /// used to calculate the actual fee and is only for informational purposes.
type WeightPrice: Convert<Weight, BalanceOf<Self>>; type WeightPrice: Convert<Weight, BalanceOf<Self>>;
@@ -213,56 +200,12 @@ pub mod pallet {
#[pallet::constant] #[pallet::constant]
type Schedule: Get<Schedule<Self>>; type Schedule: Get<Schedule<Self>>;
/// Number of block delay an extrinsic claim surcharge has. /// The deposit that must be placed into the contract's account to instantiate it.
/// /// This is in **addition** to the [`pallet_balances::Pallet::ExistenialDeposit`].
/// When claim surcharge is called by an extrinsic the rent is checked /// The minimum balance for a contract's account can be queried using
/// for current_block - delay /// [`Pallet::subsistence_threshold`].
#[pallet::constant] #[pallet::constant]
type SignedClaimHandicap: Get<Self::BlockNumber>; type ContractDeposit: Get<BalanceOf<Self>>;
/// The minimum amount required to generate a tombstone.
#[pallet::constant]
type TombstoneDeposit: Get<BalanceOf<Self>>;
/// The balance every contract needs to deposit to stay alive indefinitely.
///
/// This is different from the [`Self::TombstoneDeposit`] because this only needs to be
/// deposited while the contract is alive. Costs for additional storage are added to
/// this base cost.
///
/// This is a simple way to ensure that contracts with empty storage eventually get deleted
/// by making them pay rent. This creates an incentive to remove them early in order to save
/// rent.
#[pallet::constant]
type DepositPerContract: Get<BalanceOf<Self>>;
/// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
///
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1
/// BU/byte/day, then a contract with 1,000,000 BU that uses 1,000 bytes of storage would
/// pay no rent. But if the balance reduced to 500,000 BU and the storage stayed the same at
/// 1,000, then it would pay 500 BU/day.
#[pallet::constant]
type DepositPerStorageByte: Get<BalanceOf<Self>>;
/// The balance a contract needs to deposit per storage item to stay alive indefinitely.
///
/// It works the same as [`Self::DepositPerStorageByte`] but for storage items.
#[pallet::constant]
type DepositPerStorageItem: Get<BalanceOf<Self>>;
/// The fraction of the deposit that should be used as rent per block.
///
/// When a contract hasn't enough balance deposited to stay alive indefinitely it needs
/// to pay per block for the storage it consumes that is not covered by the deposit.
/// This determines how high this rent payment is per block as a fraction of the deposit.
#[pallet::constant]
type RentFraction: Get<Perbill>;
/// Reward that is received by the party whose touch has led
/// to removal of a contract.
#[pallet::constant]
type SurchargeReward: Get<BalanceOf<Self>>;
/// The type of the call stack determines the maximum nesting depth of contract calls. /// The type of the call stack determines the maximum nesting depth of contract calls.
/// ///
@@ -439,50 +382,6 @@ pub mod pallet {
gas_meter gas_meter
.into_dispatch_result(result, T::WeightInfo::instantiate(salt.len() as u32 / 1024)) .into_dispatch_result(result, T::WeightInfo::instantiate(salt.len() as u32 / 1024))
} }
/// Allows block producers to claim a small reward for evicting a contract. If a block
/// producer fails to do so, a regular users will be allowed to claim the reward.
///
/// In case of a successful eviction no fees are charged from the sender. However, the
/// reward is capped by the total amount of rent that was paid by the contract while
/// it was alive.
///
/// If contract is not evicted as a result of this call, [`Error::ContractNotEvictable`]
/// is returned and the sender is not eligible for the reward.
#[pallet::weight(T::WeightInfo::claim_surcharge(T::Schedule::get().limits.code_len / 1024))]
pub fn claim_surcharge(
origin: OriginFor<T>,
dest: T::AccountId,
aux_sender: Option<T::AccountId>,
) -> DispatchResultWithPostInfo {
let origin = origin.into();
let (signed, rewarded) = match (origin, aux_sender) {
(Ok(frame_system::RawOrigin::Signed(account)), None) => (true, account),
(Ok(frame_system::RawOrigin::None), Some(aux_sender)) => (false, aux_sender),
_ => Err(Error::<T>::InvalidSurchargeClaim)?,
};
// Add some advantage for block producers (who send unsigned extrinsics) by
// adding a handicap: for signed extrinsics we use a slightly older block number
// for the eviction check. This can be viewed as if we pushed regular users back in
// past.
let handicap = if signed { T::SignedClaimHandicap::get() } else { Zero::zero() };
// If poking the contract has lead to eviction of the contract, give out the rewards.
match Rent::<T, PrefabWasmModule<T>>::try_eviction(&dest, handicap)? {
(Some(rent_paid), code_len) => T::Currency::deposit_into_existing(
&rewarded,
T::SurchargeReward::get().min(rent_paid),
)
.map(|_| PostDispatchInfo {
actual_weight: Some(T::WeightInfo::claim_surcharge(code_len / 1024)),
pays_fee: Pays::No,
})
.map_err(Into::into),
(None, code_len) => Err(Error::<T>::ContractNotEvictable
.with_weight(T::WeightInfo::claim_surcharge(code_len / 1024))),
}
}
} }
#[pallet::event] #[pallet::event]
@@ -492,10 +391,7 @@ pub mod pallet {
/// Contract deployed by address at the specified address. \[deployer, contract\] /// Contract deployed by address at the specified address. \[deployer, contract\]
Instantiated(T::AccountId, T::AccountId), Instantiated(T::AccountId, T::AccountId),
/// Contract has been evicted and is now in tombstone state. \[contract\] /// Contract has been removed.
Evicted(T::AccountId),
/// Contract has been terminated without leaving a tombstone.
/// \[contract, beneficiary\] /// \[contract, beneficiary\]
/// ///
/// # Params /// # Params
@@ -505,21 +401,10 @@ pub mod pallet {
/// ///
/// # Note /// # Note
/// ///
/// The only way for a contract to be removed without a tombstone and emitting /// The only way for a contract to be removed and emitting this event is by calling
/// this event is by calling `seal_terminate`. /// `seal_terminate`.
Terminated(T::AccountId, T::AccountId), Terminated(T::AccountId, T::AccountId),
/// Restoration of a contract has been successful.
/// \[restorer, dest, code_hash, rent_allowance\]
///
/// # Params
///
/// - `restorer`: Account ID of the restoring contract.
/// - `dest`: Account ID of the restored contract.
/// - `code_hash`: Code hash of the restored contract.
/// - `rent_allowance`: Rent allowance of the restored contract.
Restored(T::AccountId, T::AccountId, T::Hash, BalanceOf<T>),
/// Code with the specified hash has been stored. \[code_hash\] /// Code with the specified hash has been stored. \[code_hash\]
CodeStored(T::Hash), CodeStored(T::Hash),
@@ -544,7 +429,7 @@ pub mod pallet {
/// A code with the specified hash was removed. /// A code with the specified hash was removed.
/// \[code_hash\] /// \[code_hash\]
/// ///
/// This happens when the last contract that uses this code hash was removed or evicted. /// This happens when the last contract that uses this code hash was removed.
CodeRemoved(T::Hash), CodeRemoved(T::Hash),
} }
@@ -552,24 +437,13 @@ pub mod pallet {
pub enum Error<T> { pub enum Error<T> {
/// A new schedule must have a greater version than the current one. /// A new schedule must have a greater version than the current one.
InvalidScheduleVersion, InvalidScheduleVersion,
/// An origin must be signed or inherent and auxiliary sender only provided on inherent.
InvalidSurchargeClaim,
/// Cannot restore from nonexisting or tombstone contract.
InvalidSourceContract,
/// Cannot restore to nonexisting or alive contract.
InvalidDestinationContract,
/// Tombstones don't match.
InvalidTombstone,
/// An origin TrieId written in the current block.
InvalidContractOrigin,
/// The executed contract exhausted its gas limit. /// The executed contract exhausted its gas limit.
OutOfGas, OutOfGas,
/// The output buffer supplied to a contract API call was too small. /// The output buffer supplied to a contract API call was too small.
OutputBufferTooSmall, OutputBufferTooSmall,
/// Performing the requested transfer would have brought the contract below /// Performing the requested transfer would have brought the contract below
/// the subsistence threshold. No transfer is allowed to do this in order to allow /// the subsistence threshold. No transfer is allowed to do this. Use `seal_terminate`
/// for a tombstone to be created. Use `seal_terminate` to remove a contract without /// to recover a deposit.
/// leaving a tombstone behind.
BelowSubsistenceThreshold, BelowSubsistenceThreshold,
/// The newly created contract is below the subsistence threshold after executing /// The newly created contract is below the subsistence threshold after executing
/// its contructor. No contracts are allowed to exist below that threshold. /// its contructor. No contracts are allowed to exist below that threshold.
@@ -583,18 +457,6 @@ pub mod pallet {
MaxCallDepthReached, MaxCallDepthReached,
/// No contract was found at the specified address. /// No contract was found at the specified address.
ContractNotFound, ContractNotFound,
/// A tombstone exist at the specified address.
///
/// Tombstone cannot be called. Anyone can use `seal_restore_to` in order to revive
/// the contract, though.
ContractIsTombstone,
/// The called contract does not have enough balance to pay for its storage.
///
/// The contract ran out of balance and is therefore eligible for eviction into a
/// tombstone. Anyone can evict the contract by submitting a `claim_surcharge`
/// extrinsic. Alternatively, a plain balance transfer can be used in order to
/// increase the contracts funds so that it can be called again.
RentNotPaid,
/// The code supplied to `instantiate_with_code` exceeds the limit specified in the /// The code supplied to `instantiate_with_code` exceeds the limit specified in the
/// current schedule. /// current schedule.
CodeTooLarge, CodeTooLarge,
@@ -609,7 +471,7 @@ pub mod pallet {
/// The size defined in `T::MaxValueSize` was exceeded. /// The size defined in `T::MaxValueSize` was exceeded.
ValueTooLarge, ValueTooLarge,
/// Termination of a contract is not allowed while the contract is already /// Termination of a contract is not allowed while the contract is already
/// on the call stack. Can be triggered by `seal_terminate` or `seal_restore_to. /// on the call stack. Can be triggered by `seal_terminate`.
TerminatedWhileReentrant, TerminatedWhileReentrant,
/// `seal_call` forwarded this contracts input. It therefore is no longer available. /// `seal_call` forwarded this contracts input. It therefore is no longer available.
InputForwarded, InputForwarded,
@@ -625,15 +487,10 @@ pub mod pallet {
NoChainExtension, NoChainExtension,
/// Removal of a contract failed because the deletion queue is full. /// Removal of a contract failed because the deletion queue is full.
/// ///
/// This can happen when either calling [`Pallet::claim_surcharge`] or `seal_terminate`. /// This can happen when calling `seal_terminate`.
/// The queue is filled by deleting contracts and emptied by a fixed amount each block. /// The queue is filled by deleting contracts and emptied by a fixed amount each block.
/// Trying again during another block is the only way to resolve this issue. /// Trying again during another block is the only way to resolve this issue.
DeletionQueueFull, DeletionQueueFull,
/// A contract could not be evicted because it has enough balance to pay rent.
///
/// This can be returned from [`Pallet::claim_surcharge`] because the target
/// contract has enough balance to pay for its rent.
ContractNotEvictable,
/// A storage modification exhausted the 32bit type that holds the storage size. /// A storage modification exhausted the 32bit type that holds the storage size.
/// ///
/// This can either happen when the accumulated storage in bytes is too large or /// This can either happen when the accumulated storage in bytes is too large or
@@ -643,7 +500,7 @@ pub mod pallet {
DuplicateContract, DuplicateContract,
/// A contract self destructed in its constructor. /// A contract self destructed in its constructor.
/// ///
/// This can be triggered by a call to `seal_terminate` or `seal_restore_to`. /// This can be triggered by a call to `seal_terminate`.
TerminatedInConstructor, TerminatedInConstructor,
/// The debug message specified to `seal_debug_message` does contain invalid UTF-8. /// The debug message specified to `seal_debug_message` does contain invalid UTF-8.
DebugMessageInvalidUTF8, DebugMessageInvalidUTF8,
@@ -730,9 +587,6 @@ where
/// ///
/// It returns the execution result, account id and the amount of used weight. /// It returns the execution result, account id and the amount of used weight.
/// ///
/// If `compute_projection` is set to `true` the result also contains the rent projection.
/// This is optional because some non trivial and stateful work is performed to compute
/// the projection. See [`Self::rent_projection`].
/// ///
/// # Note /// # Note
/// ///
@@ -746,9 +600,8 @@ where
code: Code<CodeHash<T>>, code: Code<CodeHash<T>>,
data: Vec<u8>, data: Vec<u8>,
salt: Vec<u8>, salt: Vec<u8>,
compute_projection: bool,
debug: bool, debug: bool,
) -> ContractInstantiateResult<T::AccountId, T::BlockNumber> { ) -> ContractInstantiateResult<T::AccountId> {
let mut gas_meter = GasMeter::new(gas_limit); let mut gas_meter = GasMeter::new(gas_limit);
let schedule = T::Schedule::get(); let schedule = T::Schedule::get();
let executable = match code { let executable = match code {
@@ -776,18 +629,7 @@ where
&salt, &salt,
debug_message.as_mut(), debug_message.as_mut(),
) )
.and_then(|(account_id, result)| { .and_then(|(account_id, result)| Ok(InstantiateReturnValue { result, account_id }));
let rent_projection = if compute_projection {
Some(
Rent::<T, PrefabWasmModule<T>>::compute_projection(&account_id)
.map_err(|_| <Error<T>>::NewContractNotFunded)?,
)
} else {
None
};
Ok(InstantiateReturnValue { result, account_id, rent_projection })
});
ContractInstantiateResult { ContractInstantiateResult {
result: result.map_err(|e| e.error), result: result.map_err(|e| e.error),
gas_consumed: gas_meter.gas_consumed(), gas_consumed: gas_meter.gas_consumed(),
@@ -798,21 +640,13 @@ where
/// Query storage of a specified contract under a specified key. /// Query storage of a specified contract under a specified key.
pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult { pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult {
let contract_info = ContractInfoOf::<T>::get(&address) let contract_info =
.ok_or(ContractAccessError::DoesntExist)? ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
.get_alive()
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key); let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key);
Ok(maybe_value) Ok(maybe_value)
} }
/// Query how many blocks the contract stays alive given that the amount endowment
/// and consumed storage does not change.
pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> {
Rent::<T, PrefabWasmModule<T>>::compute_projection(&address)
}
/// Determine the address of a contract, /// Determine the address of a contract,
/// ///
/// This is the address generation function used by contract instantiation. Its result /// This is the address generation function used by contract instantiation. Its result
@@ -837,14 +671,13 @@ where
} }
/// Subsistence threshold is the extension of the minimum balance (aka existential deposit) /// Subsistence threshold is the extension of the minimum balance (aka existential deposit)
/// by the tombstone deposit, required for leaving a tombstone. /// by the contract deposit. It is the minimum balance any contract must hold.
/// ///
/// Rent or any contract initiated balance transfer mechanism cannot make the balance lower /// Any contract initiated balance transfer mechanism cannot make the balance lower
/// than the subsistence threshold in order to guarantee that a tombstone is created. /// than the subsistence threshold. The only way to recover the balance is to remove
/// /// contract using `seal_terminate`.
/// The only way to completely kill a contract without a tombstone is calling `seal_terminate`.
pub fn subsistence_threshold() -> BalanceOf<T> { pub fn subsistence_threshold() -> BalanceOf<T> {
T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get()) T::Currency::minimum_balance().saturating_add(T::ContractDeposit::get())
} }
/// The in-memory size in bytes of the data structure associated with each contract. /// The in-memory size in bytes of the data structure associated with each contract.
+91 -5
View File
@@ -18,18 +18,104 @@
use crate::{Config, Pallet, Weight}; use crate::{Config, Pallet, Weight};
use frame_support::{ use frame_support::{
storage::migration, storage::migration,
traits::{Get, PalletInfoAccess, StorageVersion}, traits::{Get, PalletInfoAccess},
}; };
use sp_std::prelude::*;
pub fn migrate<T: Config>() -> Weight { pub fn migrate<T: Config>() -> Weight {
use frame_support::traits::StorageVersion;
let version = StorageVersion::get::<Pallet<T>>();
let mut weight: Weight = 0; let mut weight: Weight = 0;
if StorageVersion::get::<Pallet<T>>() == 3 { if version < 4 {
weight = weight.saturating_add(T::DbWeight::get().writes(1)); weight = weight.saturating_add(v4::migrate::<T>());
migration::remove_storage_prefix(<Pallet<T>>::name().as_bytes(), b"CurrentSchedule", b"");
StorageVersion::new(4).put::<Pallet<T>>(); StorageVersion::new(4).put::<Pallet<T>>();
} }
if version < 5 {
weight = weight.saturating_add(v5::migrate::<T>());
StorageVersion::new(5).put::<Pallet<T>>();
}
weight weight
} }
/// V4: `Schedule` is changed to be a config item rather than an in-storage value.
mod v4 {
use super::*;
pub fn migrate<T: Config>() -> Weight {
migration::remove_storage_prefix(<Pallet<T>>::name().as_bytes(), b"CurrentSchedule", b"");
T::DbWeight::get().writes(1)
}
}
/// V5: State rent is removed which obsoletes some fields in `ContractInfo`.
mod v5 {
use super::*;
use crate::{
BalanceOf, CodeHash, ContractInfo, ContractInfoOf, DeletedContract, DeletionQueue, TrieId,
};
use codec::Decode;
use sp_std::marker::PhantomData;
type AliveContractInfo<T> =
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;
type TombstoneContractInfo<T> = RawTombstoneContractInfo<
<T as frame_system::Config>::Hash,
<T as frame_system::Config>::Hashing,
>;
#[derive(Decode)]
enum OldContractInfo<T: Config> {
Alive(AliveContractInfo<T>),
Tombstone(TombstoneContractInfo<T>),
}
#[derive(Decode)]
struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
trie_id: TrieId,
_storage_size: u32,
_pair_count: u32,
code_hash: CodeHash,
_rent_allowance: Balance,
_rent_paid: Balance,
_deduct_block: BlockNumber,
_last_write: Option<BlockNumber>,
_reserved: Option<()>,
}
#[derive(Decode)]
struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
#[derive(Decode)]
struct OldDeletedContract {
_pair_count: u32,
trie_id: TrieId,
}
pub fn migrate<T: Config>() -> Weight {
let mut weight: Weight = 0;
<ContractInfoOf<T>>::translate(|_key, old: OldContractInfo<T>| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
match old {
OldContractInfo::Alive(old) => Some(ContractInfo::<T> {
trie_id: old.trie_id,
code_hash: old.code_hash,
_reserved: old._reserved,
}),
OldContractInfo::Tombstone(_) => None,
}
});
<DeletionQueue<T>>::translate(|old: Option<Vec<OldDeletedContract>>| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
old.map(|old| old.into_iter().map(|o| DeletedContract { trie_id: o.trie_id }).collect())
})
.ok();
weight
}
}
-577
View File
@@ -1,577 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A module responsible for computing the right amount of weight and charging it.
use crate::{
exec::Executable, gas::GasMeter, storage::Storage, wasm::PrefabWasmModule, AliveContractInfo,
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, Pallet,
TombstoneContractInfo,
};
use frame_support::{
storage::child,
traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons},
DefaultNoBound,
};
use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
use sp_core::crypto::UncheckedFrom;
use sp_io::hashing::blake2_256;
use sp_runtime::{
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
DispatchError,
};
use sp_std::prelude::*;
/// Information about the required deposit and resulting rent.
///
/// The easiest way to guarantee that a contract stays alive is to assert that
/// `max_rent == 0` at the **end** of a contract's execution.
///
/// # Note
///
/// The `current_*` fields do **not** consider changes to the code's refcount made during
/// the currently running call.
#[derive(codec::Encode, DefaultNoBound)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RentStatus<T: Config> {
/// Required deposit assuming that this contract is the only user of its code.
pub max_deposit: BalanceOf<T>,
/// Required deposit assuming the code's current refcount.
pub current_deposit: BalanceOf<T>,
/// Required deposit assuming the specified refcount (None if 0 is supplied).
pub custom_refcount_deposit: Option<BalanceOf<T>>,
/// Rent that is paid assuming that the contract is the only user of its code.
pub max_rent: BalanceOf<T>,
/// Rent that is paid given the code's current refcount.
pub current_rent: BalanceOf<T>,
/// Rent that is paid assuming the specified refcount (None is 0 is supplied).
pub custom_refcount_rent: Option<BalanceOf<T>>,
/// Reserved for backwards compatible changes to this data structure.
pub _reserved: Option<()>,
}
pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);
impl<T, E> Rent<T, E>
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
E: Executable<T>,
{
/// Make account paying the rent for the current block number
///
/// 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,
contract: AliveContractInfo<T>,
code_size: u32,
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
let current_block_number = <frame_system::Pallet<T>>::block_number();
let verdict =
Self::consider_case(account, current_block_number, Zero::zero(), &contract, code_size);
Self::enact_verdict(account, contract, current_block_number, verdict, None)
}
/// Process a report that a contract under the given address should be evicted.
///
/// Enact the eviction right away if the contract should be evicted and return the amount
/// of rent that the contract paid over its lifetime.
/// Otherwise, **do nothing** and return None.
///
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
/// of current block. E.g. if the contract is going to be evicted at the current block,
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain
/// snitchers relative to others.
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn try_eviction(
account: &T::AccountId,
handicap: T::BlockNumber,
) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {
let contract = <ContractInfoOf<T>>::get(account);
let contract = match contract {
None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),
Some(ContractInfo::Alive(contract)) => contract,
};
let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
let code_len = module.code_len();
let current_block_number = <frame_system::Pallet<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.
match verdict {
Verdict::Evict { ref amount } => {
// The outstanding `amount` is withdrawn inside `enact_verdict`.
let rent_paid = amount
.as_ref()
.map(|a| a.peek())
.unwrap_or_else(|| <BalanceOf<T>>::zero())
.saturating_add(contract.rent_paid);
Self::enact_verdict(
account,
contract,
current_block_number,
verdict,
Some(module),
)?;
Ok((Some(rent_paid), code_len))
},
_ => Ok((None, code_len)),
}
}
/// Returns the projected time a given contract will be able to sustain paying its rent. The
/// returned projection is relevant 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_projection(account: &T::AccountId) -> RentProjectionResult<T::BlockNumber> {
use ContractAccessError::IsTombstone;
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let module = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)
.map_err(|_| IsTombstone)?;
let code_size = module.occupied_storage();
let current_block_number = <frame_system::Pallet<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
code_size,
);
// We skip the eviction in case one is in order.
// Evictions should only be performed by [`try_eviction`].
let new_contract_info =
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict, None);
// Check what happened after enaction of the verdict.
let alive_contract_info =
new_contract_info.map_err(|_| IsTombstone)?.ok_or_else(|| IsTombstone)?;
// 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::fee_per_block(&free_balance, &alive_contract_info, code_size);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction)
}
// Then compute how much the contract will sustain under these circumstances.
let rent_budget = Self::rent_budget(&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 => {
// `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))
}
/// 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
/// - the tombstone matches the hash of the origin storage root, and code hash.
///
/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
/// the restored account. The restored account will inherit the last write block and its last
/// deduct block will be set to the current block.
pub fn restore_to(
origin: &T::AccountId,
mut origin_contract: AliveContractInfo<T>,
dest: T::AccountId,
code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>,
delta: Vec<crate::exec::StorageKey>,
gas_meter: &mut GasMeter<T>,
) -> Result<(), DispatchError> {
let child_trie_info = origin_contract.child_trie_info();
let current_block = <frame_system::Pallet<T>>::block_number();
if origin_contract.last_write == Some(current_block) {
return Err(Error::<T>::InvalidContractOrigin.into())
}
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
.and_then(|c| c.get_tombstone())
.ok_or(Error::<T>::InvalidDestinationContract)?;
let last_write =
if !delta.is_empty() { Some(current_block) } else { origin_contract.last_write };
// Fails if the code hash does not exist on chain
E::add_user(code_hash, gas_meter)?;
// 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.
// The failure of this function will lead to this transaction's rollback.
let bytes_taken: u32 = delta
.iter()
.filter_map(|key| {
let key = blake2_256(key);
child::get_raw(&child_trie_info, &key).map(|value| {
child::kill(&child_trie_info, &key);
value.len() as u32
})
})
.sum();
let tombstone = <TombstoneContractInfo<T>>::new(
// This operation is cheap enough because last_write (delta not included)
// is not this block as it has been checked earlier.
&child::root(&child_trie_info)[..],
code_hash,
);
if tombstone != dest_tombstone {
return Err(Error::<T>::InvalidTombstone.into())
}
origin_contract.storage_size -= bytes_taken;
<ContractInfoOf<T>>::remove(&origin);
E::remove_user(origin_contract.code_hash, gas_meter)?;
<ContractInfoOf<T>>::insert(
&dest,
ContractInfo::Alive(AliveContractInfo::<T> {
code_hash,
rent_allowance,
rent_paid: <BalanceOf<T>>::zero(),
deduct_block: current_block,
last_write,
..origin_contract
}),
);
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(())
}
/// Create a new `RentStatus` struct for pass through to a requesting contract.
pub fn rent_status(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
aggregated_code_size: u32,
current_refcount: u32,
at_refcount: u32,
) -> RentStatus<T> {
let calc_share = |refcount: u32| aggregated_code_size.checked_div(refcount).unwrap_or(0);
let current_share = calc_share(current_refcount);
let custom_share = calc_share(at_refcount);
RentStatus {
max_deposit: Self::required_deposit(contract, aggregated_code_size),
current_deposit: Self::required_deposit(contract, current_share),
custom_refcount_deposit: if at_refcount > 0 {
Some(Self::required_deposit(contract, custom_share))
} else {
None
},
max_rent: Self::fee_per_block(free_balance, contract, aggregated_code_size),
current_rent: Self::fee_per_block(free_balance, contract, current_share),
custom_refcount_rent: if at_refcount > 0 {
Some(Self::fee_per_block(free_balance, contract, custom_share))
} else {
None
},
_reserved: None,
}
}
/// Returns how much deposit is required to not pay rent.
fn required_deposit(contract: &AliveContractInfo<T>, code_size_share: u32) -> BalanceOf<T> {
T::DepositPerStorageByte::get()
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
.saturating_add(
T::DepositPerStorageItem::get().saturating_mul(contract.pair_count.into()),
)
.saturating_add(T::DepositPerContract::get())
}
/// 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 fee_per_block(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
code_size_share: u32,
) -> BalanceOf<T> {
let missing_deposit =
Self::required_deposit(contract, code_size_share).saturating_sub(*free_balance);
T::RentFraction::get().mul_ceil(missing_deposit)
}
/// 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 toal_balance is below the subsistence threshold, this function returns `None`.
fn rent_budget(
total_balance: &BalanceOf<T>,
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = Pallet::<T>::subsistence_threshold();
// Reserved balance contributes towards the subsistence threshold to stay consistent
// with the existential deposit where the reserved balance is also counted.
if *total_balance < subsistence_threshold {
return None
}
// However, reserved balance cannot be charged so we need to use the free balance
// to calculate the actual budget (which can be 0).
let rent_allowed_to_charge = free_balance.saturating_sub(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`.
///
/// Use `handicap` in case you want to change the reference block number. (To get more details
/// see `try_eviction` ).
fn consider_case(
account: &T::AccountId,
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 = {
// Calculate an effective block number, i.e. after adjusting for handicap.
let effective_block_number = current_block_number.saturating_sub(handicap);
effective_block_number.saturating_sub(contract.deduct_block)
};
if blocks_passed.is_zero() {
// Rent has already been paid
return Verdict::Exempt
}
let total_balance = T::Currency::total_balance(account);
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::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.
return Verdict::Exempt
}
let rent_budget = match Self::rent_budget(&total_balance, &free_balance, contract) {
Some(rent_budget) => rent_budget,
None => {
// All functions that allow a contract to transfer balance enforce
// that the contract always stays above the subsistence threshold.
// We want the rent system to always leave a tombstone to prevent the
// accidental loss of a contract. Ony `seal_terminate` can remove a
// contract without a tombstone. Therefore this case should be never
// hit.
log::error!(
target: "runtime::contracts",
"Tombstoned a contract that is below the subsistence threshold: {:?}",
account,
);
0u32.into()
},
};
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;
// If the rent payment cannot be withdrawn due to locks on the account balance, then evict
// the account.
//
// NOTE: This seems problematic because it provides a way to tombstone an account while
// avoiding the last rent payment. In effect, someone could retroactively set rent_allowance
// for their contract to 0.
let dues_limited = dues.min(rent_budget);
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
account,
dues_limited,
WithdrawReasons::FEE,
free_balance.saturating_sub(dues_limited),
)
.is_ok();
if insufficient_rent || !can_withdraw_rent {
// The contract cannot afford the rent payment and has a balance above the subsistence
// threshold, so it leaves a tombstone.
let amount =
if can_withdraw_rent { Some(OutstandingAmount::new(dues_limited)) } else { None };
return Verdict::Evict { amount }
}
return Verdict::Charge {
// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
amount: OutstandingAmount::new(dues_limited),
}
}
/// 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>,
evictable_code: Option<PrefabWasmModule<T>>,
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
match (verdict, evictable_code) {
(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`.
Storage::<T>::queue_trie_for_deletion(&alive_contract_info)?;
if let Some(amount) = amount {
amount.withdraw(account);
}
// Note: this operation is heavy.
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);
code.drop_from_storage();
<Pallet<T>>::deposit_event(Event::Evicted(account.clone()));
Ok(None)
},
(Verdict::Evict { amount: _ }, None) => Ok(None),
(Verdict::Exempt, _) => {
let contract = ContractInfo::Alive(AliveContractInfo::<T> {
deduct_block: current_block_number,
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract);
Ok(Some(contract.get_alive().expect("We just constructed it as alive. qed")))
},
(Verdict::Charge { amount }, _) => {
let contract = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
rent_paid: alive_contract_info.rent_paid.saturating_add(amount.peek()),
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract);
amount.withdraw(account);
Ok(Some(contract.get_alive().expect("We just constructed it as alive. qed")))
},
}
}
}
/// The amount to charge.
///
/// This amount respects the contract's rent allowance and the subsistence deposit.
/// Because of that, charging the amount cannot remove the contract.
struct OutstandingAmount<T: Config> {
amount: BalanceOf<T>,
}
impl<T: Config> OutstandingAmount<T> {
/// Create the new outstanding amount.
///
/// The amount should be always withdrawable and it should not kill the account.
fn new(amount: BalanceOf<T>) -> Self {
Self { amount }
}
/// Returns the amount this instance wraps.
fn peek(&self) -> BalanceOf<T> {
self.amount
}
/// Withdraws the outstanding amount from the given account.
fn withdraw(self, account: &T::AccountId) {
if let Ok(imbalance) = T::Currency::withdraw(
account,
self.amount,
WithdrawReasons::FEE,
ExistenceRequirement::KeepAlive,
) {
// This should never fail. However, let's err on the safe side.
T::RentPayment::on_unbalanced(imbalance);
}
}
}
enum Verdict<T: Config> {
/// The contract is exempted from paying rent.
///
/// For example, it already paid its rent in the current block, or it has enough deposit for
/// not paying rent at all.
Exempt,
/// The contract cannot afford payment within its rent budget so it gets evicted. However,
/// because its balance is greater than the subsistence threshold it leaves a tombstone.
Evict { amount: Option<OutstandingAmount<T>> },
/// Everything is OK, we just only take some charge.
Charge { amount: OutstandingAmount<T> },
}
+3 -19
View File
@@ -267,11 +267,8 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_minimum_balance`. /// Weight of calling `seal_minimum_balance`.
pub minimum_balance: Weight, pub minimum_balance: Weight,
/// Weight of calling `seal_tombstone_deposit`. /// Weight of calling `seal_contract_deposit`.
pub tombstone_deposit: Weight, pub contract_deposit: Weight,
/// Weight of calling `seal_rent_allowance`.
pub rent_allowance: Weight,
/// Weight of calling `seal_block_number`. /// Weight of calling `seal_block_number`.
pub block_number: Weight, pub block_number: Weight,
@@ -300,12 +297,6 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_terminate`. /// Weight of calling `seal_terminate`.
pub terminate: Weight, pub terminate: Weight,
/// Weight of calling `seal_restore_to`.
pub restore_to: Weight,
/// Weight per delta key supplied to `seal_restore_to`.
pub restore_to_per_delta: Weight,
/// Weight of calling `seal_random`. /// Weight of calling `seal_random`.
pub random: Weight, pub random: Weight,
@@ -321,9 +312,6 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_debug_message`. /// Weight of calling `seal_debug_message`.
pub debug_message: Weight, pub debug_message: Weight,
/// Weight of calling `seal_set_rent_allowance`.
pub set_rent_allowance: Weight,
/// Weight of calling `seal_set_storage`. /// Weight of calling `seal_set_storage`.
pub set_storage: Weight, pub set_storage: Weight,
@@ -566,8 +554,7 @@ impl<T: Config> Default for HostFnWeights<T> {
balance: cost_batched!(seal_balance), balance: cost_batched!(seal_balance),
value_transferred: cost_batched!(seal_value_transferred), value_transferred: cost_batched!(seal_value_transferred),
minimum_balance: cost_batched!(seal_minimum_balance), minimum_balance: cost_batched!(seal_minimum_balance),
tombstone_deposit: cost_batched!(seal_tombstone_deposit), contract_deposit: cost_batched!(seal_tombstone_deposit),
rent_allowance: cost_batched!(seal_rent_allowance),
block_number: cost_batched!(seal_block_number), block_number: cost_batched!(seal_block_number),
now: cost_batched!(seal_now), now: cost_batched!(seal_now),
weight_to_fee: cost_batched!(seal_weight_to_fee), weight_to_fee: cost_batched!(seal_weight_to_fee),
@@ -577,8 +564,6 @@ impl<T: Config> Default for HostFnWeights<T> {
r#return: cost!(seal_return), r#return: cost!(seal_return),
return_per_byte: cost_byte!(seal_return_per_kb), return_per_byte: cost_byte!(seal_return_per_kb),
terminate: cost!(seal_terminate), terminate: cost!(seal_terminate),
restore_to: cost!(seal_restore_to),
restore_to_per_delta: cost_batched!(seal_restore_to_per_delta),
random: cost_batched!(seal_random), random: cost_batched!(seal_random),
deposit_event: cost_batched!(seal_deposit_event), deposit_event: cost_batched!(seal_deposit_event),
deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0), deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0),
@@ -588,7 +573,6 @@ impl<T: Config> Default for HostFnWeights<T> {
1 1
), ),
debug_message: cost_batched!(seal_debug_message), debug_message: cost_batched!(seal_debug_message),
set_rent_allowance: cost_batched!(seal_set_rent_allowance),
set_storage: cost_batched!(seal_set_storage), set_storage: cost_batched!(seal_set_storage),
set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb), set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb),
clear_storage: cost_batched!(seal_clear_storage), clear_storage: cost_batched!(seal_clear_storage),
+32 -190
View File
@@ -20,9 +20,9 @@
use crate::{ use crate::{
exec::{AccountIdOf, StorageKey}, exec::{AccountIdOf, StorageKey},
weights::WeightInfo, weights::WeightInfo,
BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId,
}; };
use codec::{Codec, Decode, Encode}; use codec::{Decode, Encode};
use frame_support::{ use frame_support::{
dispatch::{DispatchError, DispatchResult}, dispatch::{DispatchError, DispatchResult},
storage::child::{self, ChildInfo, KillStorageResult}, storage::child::{self, ChildInfo, KillStorageResult},
@@ -31,87 +31,26 @@ use frame_support::{
}; };
use sp_core::crypto::UncheckedFrom; use sp_core::crypto::UncheckedFrom;
use sp_io::hashing::blake2_256; use sp_io::hashing::blake2_256;
use sp_runtime::{ use sp_runtime::{traits::Hash, RuntimeDebug};
traits::{Bounded, Hash, MaybeSerializeDeserialize, Member, Saturating, Zero}, use sp_std::{marker::PhantomData, prelude::*};
RuntimeDebug,
};
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
pub type AliveContractInfo<T> = pub type ContractInfo<T> = RawContractInfo<CodeHash<T>>;
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;
pub type TombstoneContractInfo<T> = RawTombstoneContractInfo<
<T as frame_system::Config>::Hash,
<T as frame_system::Config>::Hashing,
>;
/// Information for managing an account and its sub trie abstraction.
/// This is the required info to cache for an account
#[derive(Encode, Decode, RuntimeDebug)]
pub enum ContractInfo<T: Config> {
Alive(AliveContractInfo<T>),
Tombstone(TombstoneContractInfo<T>),
}
impl<T: Config> ContractInfo<T> {
/// If contract is alive then return some alive info
pub fn get_alive(self) -> Option<AliveContractInfo<T>> {
if let ContractInfo::Alive(alive) = self {
Some(alive)
} else {
None
}
}
/// If contract is alive then return some reference to alive info
#[cfg(test)]
pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {
if let ContractInfo::Alive(ref alive) = self {
Some(alive)
} else {
None
}
}
/// If contract is tombstone then return some tombstone info
pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(tombstone) = self {
Some(tombstone)
} else {
None
}
}
}
/// Information for managing an account and its sub trie abstraction. /// Information for managing an account and its sub trie abstraction.
/// This is the required info to cache for an account. /// This is the required info to cache for an account.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> { pub struct RawContractInfo<CodeHash> {
/// Unique ID for the subtree encoded as a bytes vector. /// Unique ID for the subtree encoded as a bytes vector.
pub trie_id: TrieId, pub trie_id: TrieId,
/// The total number of bytes used by this contract.
///
/// It is a sum of each key-value pair stored by this contract.
pub storage_size: u32,
/// The total number of key-value pairs in storage of this contract.
pub pair_count: u32,
/// The code associated with a given account. /// The code associated with a given account.
pub code_hash: CodeHash, pub code_hash: CodeHash,
/// Pay rent at most up to this value.
pub rent_allowance: Balance,
/// The amount of rent that was paid by the contract over its whole lifetime.
///
/// A restored contract starts with a value of zero just like a new contract.
pub rent_paid: Balance,
/// Last block rent has been paid.
pub deduct_block: BlockNumber,
/// Last block child storage has been written.
pub last_write: Option<BlockNumber>,
/// This field is reserved for future evolution of format. /// This field is reserved for future evolution of format.
pub _reserved: Option<()>, pub _reserved: Option<()>,
} }
impl<CodeHash, Balance, BlockNumber> RawAliveContractInfo<CodeHash, Balance, BlockNumber> { impl<CodeHash> RawContractInfo<CodeHash> {
/// Associated child trie unique id is built from the hash part of the trie id. /// Associated child trie unique id is built from the hash part of the trie id.
#[cfg(test)]
pub fn child_trie_info(&self) -> ChildInfo { pub fn child_trie_info(&self) -> ChildInfo {
child_trie_info(&self.trie_id[..]) child_trie_info(&self.trie_id[..])
} }
@@ -122,40 +61,9 @@ fn child_trie_info(trie_id: &[u8]) -> ChildInfo {
ChildInfo::new_default(trie_id) ChildInfo::new_default(trie_id)
} }
#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
where
H: Member
+ MaybeSerializeDeserialize
+ Debug
+ AsRef<[u8]>
+ AsMut<[u8]>
+ Copy
+ Default
+ sp_std::hash::Hash
+ Codec,
Hasher: Hash<Output = H>,
{
pub fn new(storage_root: &[u8], code_hash: H) -> Self {
let mut buf = Vec::new();
storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));
buf.extend_from_slice(code_hash.as_ref());
RawTombstoneContractInfo(<Hasher as Hash>::hash(&buf[..]), PhantomData)
}
}
impl<T: Config> From<AliveContractInfo<T>> for ContractInfo<T> {
fn from(alive_info: AliveContractInfo<T>) -> Self {
Self::Alive(alive_info)
}
}
#[derive(Encode, Decode)] #[derive(Encode, Decode)]
pub struct DeletedContract { pub struct DeletedContract {
pair_count: u32, pub(crate) trie_id: TrieId,
trie_id: TrieId,
} }
pub struct Storage<T>(PhantomData<T>); pub struct Storage<T>(PhantomData<T>);
@@ -181,47 +89,13 @@ where
/// contract owns, the last block the storage was written to, etc. That's why, in contrast to /// contract owns, the last block the storage was written to, etc. That's why, in contrast to
/// `read`, this function also requires the `account` ID. /// `read`, this function also requires the `account` ID.
pub fn write( pub fn write(
block_number: T::BlockNumber, new_info: &mut ContractInfo<T>,
new_info: &mut AliveContractInfo<T>,
key: &StorageKey, key: &StorageKey,
opt_new_value: Option<Vec<u8>>, opt_new_value: Option<Vec<u8>>,
) -> DispatchResult { ) -> DispatchResult {
let hashed_key = blake2_256(key); let hashed_key = blake2_256(key);
let child_trie_info = &child_trie_info(&new_info.trie_id); let child_trie_info = &child_trie_info(&new_info.trie_id);
let opt_prev_len = child::len(&child_trie_info, &hashed_key);
// Update the total number of KV pairs and the number of empty pairs.
match (&opt_prev_len, &opt_new_value) {
(Some(_), None) => {
new_info.pair_count = new_info
.pair_count
.checked_sub(1)
.ok_or_else(|| Error::<T>::StorageExhausted)?;
},
(None, Some(_)) => {
new_info.pair_count = new_info
.pair_count
.checked_add(1)
.ok_or_else(|| Error::<T>::StorageExhausted)?;
},
(Some(_), Some(_)) => {},
(None, None) => {},
}
// Update the total storage size.
let prev_value_len = opt_prev_len.unwrap_or(0);
let new_value_len =
opt_new_value.as_ref().map(|new_value| new_value.len() as u32).unwrap_or(0);
new_info.storage_size = new_info
.storage_size
.checked_sub(prev_value_len)
.and_then(|val| val.checked_add(new_value_len))
.ok_or_else(|| Error::<T>::StorageExhausted)?;
new_info.last_write = Some(block_number);
// Finally, perform the change on the storage.
match opt_new_value { match opt_new_value {
Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]), Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]),
None => child::kill(&child_trie_info, &hashed_key), None => child::kill(&child_trie_info, &hashed_key),
@@ -233,47 +107,29 @@ where
/// Creates a new contract descriptor in the storage with the given code hash at the given /// Creates a new contract descriptor in the storage with the given code hash at the given
/// address. /// address.
/// ///
/// Returns `Err` if there is already a contract (or a tombstone) exists at the given address. /// Returns `Err` if there is already a contract at the given address.
pub fn new_contract( pub fn new_contract(
account: &AccountIdOf<T>, account: &AccountIdOf<T>,
trie_id: TrieId, trie_id: TrieId,
ch: CodeHash<T>, ch: CodeHash<T>,
) -> Result<AliveContractInfo<T>, DispatchError> { ) -> Result<ContractInfo<T>, DispatchError> {
if <ContractInfoOf<T>>::contains_key(account) { if <ContractInfoOf<T>>::contains_key(account) {
return Err(Error::<T>::DuplicateContract.into()) return Err(Error::<T>::DuplicateContract.into())
} }
let contract = AliveContractInfo::<T> { let contract = ContractInfo::<T> { code_hash: ch, trie_id, _reserved: None };
code_hash: ch,
storage_size: 0,
trie_id,
deduct_block:
// We want to charge rent for the first block in advance. Therefore we
// treat the contract as if it was created in the last block and then
// charge rent for it during instantiation.
<frame_system::Pallet<T>>::block_number().saturating_sub(1u32.into()),
rent_allowance: <BalanceOf<T>>::max_value(),
rent_paid: <BalanceOf<T>>::zero(),
pair_count: 0,
last_write: None,
_reserved: None,
};
Ok(contract) Ok(contract)
} }
/// Push a contract's trie to the deletion queue for lazy removal. /// Push a contract's trie to the deletion queue for lazy removal.
/// ///
/// You must make sure that the contract is also removed or converted into a tombstone /// You must make sure that the contract is also removed when queuing the trie for deletion.
/// when queuing the trie for deletion. pub fn queue_trie_for_deletion(contract: &ContractInfo<T>) -> DispatchResult {
pub fn queue_trie_for_deletion(contract: &AliveContractInfo<T>) -> DispatchResult {
if <DeletionQueue<T>>::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize { if <DeletionQueue<T>>::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize {
Err(Error::<T>::DeletionQueueFull.into()) Err(Error::<T>::DeletionQueueFull.into())
} else { } else {
<DeletionQueue<T>>::append(DeletedContract { <DeletionQueue<T>>::append(DeletedContract { trie_id: contract.trie_id.clone() });
pair_count: contract.pair_count,
trie_id: contract.trie_id.clone(),
});
Ok(()) Ok(())
} }
} }
@@ -302,12 +158,11 @@ where
/// Delete as many items from the deletion queue possible within the supplied weight limit. /// Delete as many items from the deletion queue possible within the supplied weight limit.
/// ///
/// It returns the amount of weight used for that task or `None` when no weight was used /// It returns the amount of weight used for that task.
/// apart from the base weight.
pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight { pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight {
let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0); let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0);
if queue_len == 0 { if queue_len == 0 {
return weight_limit return 0
} }
let (weight_per_key, mut remaining_key_budget) = let (weight_per_key, mut remaining_key_budget) =
@@ -322,33 +177,20 @@ where
let mut queue = <DeletionQueue<T>>::get(); let mut queue = <DeletionQueue<T>>::get();
while !queue.is_empty() && remaining_key_budget > 0 { if let (Some(trie), true) = (queue.get(0), remaining_key_budget > 0) {
// Cannot panic due to loop condition
let trie = &mut queue[0];
let pair_count = trie.pair_count;
let outcome = let outcome =
child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget)); child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget));
if pair_count > remaining_key_budget { let keys_removed = match outcome {
// Cannot underflow because of the if condition // This should not happen as our budget was large enough to remove all keys.
trie.pair_count -= remaining_key_budget; KillStorageResult::SomeRemaining(count) => count,
} else { KillStorageResult::AllRemoved(count) => {
// We do not care to preserve order. The contract is deleted already and // We do not care to preserve order. The contract is deleted already and
// noone waits for the trie to be deleted. // noone waits for the trie to be deleted.
let removed = queue.swap_remove(0); queue.swap_remove(0);
match outcome { count
// This should not happen as our budget was large enough to remove all keys. },
KillStorageResult::SomeRemaining(_) => { };
log::error!( remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed);
target: "runtime::contracts",
"After deletion keys are remaining in this child trie: {:?}",
removed.trie_id,
);
},
KillStorageResult::AllRemoved(_) => (),
}
}
remaining_key_budget =
remaining_key_budget.saturating_sub(remaining_key_budget.min(pair_count));
} }
<DeletionQueue<T>>::put(queue); <DeletionQueue<T>>::put(queue);
@@ -365,14 +207,14 @@ where
/// Returns the code hash of the contract specified by `account` ID. /// Returns the code hash of the contract specified by `account` ID.
#[cfg(test)] #[cfg(test)]
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> { pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash)) <ContractInfoOf<T>>::get(account).map(|i| i.code_hash)
} }
/// Fill up the queue in order to exercise the limits during testing. /// Fill up the queue in order to exercise the limits during testing.
#[cfg(test)] #[cfg(test)]
pub fn fill_queue_with_dummies() { pub fn fill_queue_with_dummies() {
let queue: Vec<_> = (0..T::DeletionQueueDepth::get()) let queue: Vec<_> = (0..T::DeletionQueueDepth::get())
.map(|_| DeletedContract { pair_count: 0, trie_id: vec![] }) .map(|_| DeletedContract { trie_id: vec![] })
.collect(); .collect();
<DeletionQueue<T>>::put(queue); <DeletionQueue<T>>::put(queue);
} }
File diff suppressed because it is too large Load Diff
@@ -64,22 +64,6 @@ where
}); });
} }
/// Decrement the refcount and store.
///
/// Removes the code instead of storing it when the refcount drops to zero.
pub fn store_decremented<T: Config>(mut prefab_module: PrefabWasmModule<T>)
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
prefab_module.refcount = prefab_module.refcount.saturating_sub(1);
if prefab_module.refcount > 0 {
<CodeStorage<T>>::insert(prefab_module.code_hash, prefab_module);
} else {
<CodeStorage<T>>::remove(prefab_module.code_hash);
finish_removal::<T>(prefab_module.code_hash);
}
}
/// Increment the refcount of a code in-storage by one. /// Increment the refcount of a code in-storage by one.
pub fn increment_refcount<T: Config>( pub fn increment_refcount<T: Config>(
code_hash: CodeHash<T>, code_hash: CodeHash<T>,
+8 -125
View File
@@ -61,7 +61,7 @@ pub struct PrefabWasmModule<T: Config> {
/// The maximum memory size of a contract's sandbox. /// The maximum memory size of a contract's sandbox.
#[codec(compact)] #[codec(compact)]
maximum: u32, maximum: u32,
/// The number of alive contracts that use this as their contract code. /// The number of contracts that use this as their contract code.
/// ///
/// If this number drops to zero this module is removed from storage. /// If this number drops to zero this module is removed from storage.
#[codec(compact)] #[codec(compact)]
@@ -164,10 +164,6 @@ where
code_cache::load(code_hash, None) code_cache::load(code_hash, None)
} }
fn drop_from_storage(self) {
code_cache::store_decremented(self);
}
fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>) -> Result<(), DispatchError> { fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>) -> Result<(), DispatchError> {
code_cache::increment_refcount::<T>(code_hash, gas_meter) code_cache::increment_refcount::<T>(code_hash, gas_meter)
} }
@@ -240,11 +236,9 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
exec::{ exec::{
AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, RentParams, AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, SeedOf, StorageKey,
SeedOf, StorageKey,
}, },
gas::GasMeter, gas::GasMeter,
rent::RentStatus,
tests::{Call, Test, ALICE, BOB}, tests::{Call, Test, ALICE, BOB},
BalanceOf, CodeHash, Error, Pallet as Contracts, BalanceOf, CodeHash, Error, Pallet as Contracts,
}; };
@@ -261,14 +255,6 @@ mod tests {
use sp_runtime::DispatchError; use sp_runtime::DispatchError;
use std::{borrow::BorrowMut, cell::RefCell, collections::HashMap}; use std::{borrow::BorrowMut, cell::RefCell, collections::HashMap};
#[derive(Debug, PartialEq, Eq)]
struct RestoreEntry {
dest: AccountIdOf<Test>,
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct InstantiateEntry { struct InstantiateEntry {
code_hash: H256, code_hash: H256,
@@ -299,17 +285,14 @@ mod tests {
pub struct MockExt { pub struct MockExt {
storage: HashMap<StorageKey, Vec<u8>>, storage: HashMap<StorageKey, Vec<u8>>,
rent_allowance: u64,
instantiates: Vec<InstantiateEntry>, instantiates: Vec<InstantiateEntry>,
terminations: Vec<TerminationEntry>, terminations: Vec<TerminationEntry>,
calls: Vec<CallEntry>, calls: Vec<CallEntry>,
transfers: Vec<TransferEntry>, transfers: Vec<TransferEntry>,
restores: Vec<RestoreEntry>,
// (topics, data) // (topics, data)
events: Vec<(Vec<H256>, Vec<u8>)>, events: Vec<(Vec<H256>, Vec<u8>)>,
runtime_calls: RefCell<Vec<Call>>, runtime_calls: RefCell<Vec<Call>>,
schedule: Schedule<Test>, schedule: Schedule<Test>,
rent_params: RentParams<Test>,
gas_meter: GasMeter<Test>, gas_meter: GasMeter<Test>,
debug_buffer: Vec<u8>, debug_buffer: Vec<u8>,
} }
@@ -323,16 +306,13 @@ mod tests {
fn default() -> Self { fn default() -> Self {
Self { Self {
storage: Default::default(), storage: Default::default(),
rent_allowance: Default::default(),
instantiates: Default::default(), instantiates: Default::default(),
terminations: Default::default(), terminations: Default::default(),
calls: Default::default(), calls: Default::default(),
transfers: Default::default(), transfers: Default::default(),
restores: Default::default(),
events: Default::default(), events: Default::default(),
runtime_calls: Default::default(), runtime_calls: Default::default(),
schedule: Default::default(), schedule: Default::default(),
rent_params: Default::default(),
gas_meter: GasMeter::new(10_000_000_000), gas_meter: GasMeter::new(10_000_000_000),
debug_buffer: Default::default(), debug_buffer: Default::default(),
} }
@@ -381,16 +361,6 @@ mod tests {
self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() }); self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() });
Ok(()) Ok(())
} }
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
) -> Result<(), DispatchError> {
self.restores.push(RestoreEntry { dest, code_hash, rent_allowance, delta });
Ok(())
}
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> { fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned() self.storage.get(key).cloned()
} }
@@ -416,7 +386,7 @@ mod tests {
fn minimum_balance(&self) -> u64 { fn minimum_balance(&self) -> u64 {
666 666
} }
fn tombstone_deposit(&self) -> u64 { fn contract_deposit(&self) -> u64 {
16 16
} }
fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>) { fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>) {
@@ -425,12 +395,6 @@ mod tests {
fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) { fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) {
self.events.push((topics, data)) self.events.push((topics, data))
} }
fn set_rent_allowance(&mut self, rent_allowance: u64) {
self.rent_allowance = rent_allowance;
}
fn rent_allowance(&mut self) -> u64 {
self.rent_allowance
}
fn block_number(&self) -> u64 { fn block_number(&self) -> u64 {
121 121
} }
@@ -443,12 +407,6 @@ mod tests {
fn schedule(&self) -> &Schedule<Self::T> { fn schedule(&self) -> &Schedule<Self::T> {
&self.schedule &self.schedule
} }
fn rent_params(&self) -> &RentParams<Self::T> {
&self.rent_params
}
fn rent_status(&mut self, _at_refcount: u32) -> RentStatus<Self::T> {
Default::default()
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> { fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.gas_meter &mut self.gas_meter
} }
@@ -1380,9 +1338,9 @@ mod tests {
assert_ok!(execute(CODE_MINIMUM_BALANCE, vec![], MockExt::default())); assert_ok!(execute(CODE_MINIMUM_BALANCE, vec![], MockExt::default()));
} }
const CODE_TOMBSTONE_DEPOSIT: &str = r#" const CODE_CONTRACT_DEPOSIT: &str = r#"
(module (module
(import "seal0" "seal_tombstone_deposit" (func $seal_tombstone_deposit (param i32 i32))) (import "seal0" "seal_contract_deposit" (func $seal_contract_deposit (param i32 i32)))
(import "env" "memory" (memory 1 1)) (import "env" "memory" (memory 1 1))
;; size of our buffer is 32 bytes ;; size of our buffer is 32 bytes
@@ -1398,7 +1356,7 @@ mod tests {
) )
(func (export "call") (func (export "call")
(call $seal_tombstone_deposit (i32.const 0) (i32.const 32)) (call $seal_contract_deposit (i32.const 0) (i32.const 32))
;; assert len == 8 ;; assert len == 8
(call $assert (call $assert
@@ -1421,8 +1379,8 @@ mod tests {
"#; "#;
#[test] #[test]
fn tombstone_deposit() { fn contract_deposit() {
assert_ok!(execute(CODE_TOMBSTONE_DEPOSIT, vec![], MockExt::default())); assert_ok!(execute(CODE_CONTRACT_DEPOSIT, vec![], MockExt::default()));
} }
const CODE_RANDOM: &str = r#" const CODE_RANDOM: &str = r#"
@@ -1856,81 +1814,6 @@ mod tests {
assert_ok!(result); assert_ok!(result);
} }
#[test]
#[cfg(feature = "unstable-interface")]
fn rent_params_work() {
const CODE_RENT_PARAMS: &str = r#"
(module
(import "__unstable__" "seal_rent_params" (func $seal_rent_params (param i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 4) buffer size = 128 bytes
(data (i32.const 0) "\80")
;; [4; inf) buffer where the result is copied
(func (export "call")
;; Load the rent params into memory
(call $seal_rent_params
(i32.const 4) ;; Pointer to the output buffer
(i32.const 0) ;; Pointer to the size of the buffer
)
;; Return the contents of the buffer
(call $seal_return
(i32.const 0) ;; return flags
(i32.const 4) ;; buffer pointer
(i32.load (i32.const 0)) ;; buffer size
)
)
(func (export "deploy"))
)
"#;
let output = execute(CODE_RENT_PARAMS, vec![], MockExt::default()).unwrap();
let rent_params = Bytes(<RentParams<Test>>::default().encode());
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
}
#[test]
#[cfg(feature = "unstable-interface")]
fn rent_status_works() {
const CODE_RENT_STATUS: &str = r#"
(module
(import "__unstable__" "seal_rent_status" (func $seal_rent_status (param i32 i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 4) buffer size = 128 bytes
(data (i32.const 0) "\80")
;; [4; inf) buffer where the result is copied
(func (export "call")
;; Load the rent params into memory
(call $seal_rent_status
(i32.const 1) ;; at_refcount
(i32.const 4) ;; Pointer to the output buffer
(i32.const 0) ;; Pointer to the size of the buffer
)
;; Return the contents of the buffer
(call $seal_return
(i32.const 0) ;; return flags
(i32.const 4) ;; buffer pointer
(i32.load (i32.const 0)) ;; buffer size
)
)
(func (export "deploy"))
)
"#;
let output = execute(CODE_RENT_STATUS, vec![], MockExt::default()).unwrap();
let rent_status = Bytes(<RentStatus<Test>>::default().encode());
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_status });
}
#[test] #[test]
fn debug_message_works() { fn debug_message_works() {
const CODE_DEBUG_MESSAGE: &str = r#" const CODE_DEBUG_MESSAGE: &str = r#"
+65 -212
View File
@@ -31,6 +31,7 @@ use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
use pwasm_utils::parity_wasm::elements::ValueType; use pwasm_utils::parity_wasm::elements::ValueType;
use sp_core::{crypto::UncheckedFrom, Bytes}; use sp_core::{crypto::UncheckedFrom, Bytes};
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
use sp_runtime::traits::Bounded;
use sp_std::prelude::*; use sp_std::prelude::*;
/// Every error that can be returned to a contract when it calls any of the host functions. /// Every error that can be returned to a contract when it calls any of the host functions.
@@ -64,8 +65,7 @@ pub enum ReturnCode {
NewContractNotFunded = 6, NewContractNotFunded = 6,
/// No code could be found at the supplied code hash. /// No code could be found at the supplied code hash.
CodeNotFound = 7, CodeNotFound = 7,
/// The contract that was called is either no contract at all (a plain account) /// The contract that was called is no contract (a plain account).
/// or is a tombstone.
NotCallable = 8, NotCallable = 8,
/// The call to `seal_debug_message` had no effect because debug message /// The call to `seal_debug_message` had no effect because debug message
/// recording was disabled. /// recording was disabled.
@@ -121,8 +121,6 @@ pub enum TrapReason {
/// Signals that a trap was generated in response to a successful call to the /// Signals that a trap was generated in response to a successful call to the
/// `seal_terminate` host function. /// `seal_terminate` host function.
Termination, Termination,
/// Signals that a trap was generated because of a successful restoration.
Restoration,
} }
impl<T: Into<DispatchError>> From<T> for TrapReason { impl<T: Into<DispatchError>> From<T> for TrapReason {
@@ -149,10 +147,8 @@ pub enum RuntimeCosts {
ValueTransferred, ValueTransferred,
/// Weight of calling `seal_minimum_balance`. /// Weight of calling `seal_minimum_balance`.
MinimumBalance, MinimumBalance,
/// Weight of calling `seal_tombstone_deposit`. /// Weight of calling `seal_contract_deposit`.
TombstoneDeposit, ContractDeposit,
/// Weight of calling `seal_rent_allowance`.
RentAllowance,
/// Weight of calling `seal_block_number`. /// Weight of calling `seal_block_number`.
BlockNumber, BlockNumber,
/// Weight of calling `seal_now`. /// Weight of calling `seal_now`.
@@ -167,16 +163,12 @@ pub enum RuntimeCosts {
Return(u32), Return(u32),
/// Weight of calling `seal_terminate`. /// Weight of calling `seal_terminate`.
Terminate, Terminate,
/// Weight of calling `seal_restore_to` per number of supplied delta entries.
RestoreTo(u32),
/// Weight of calling `seal_random`. It includes the weight for copying the subject. /// Weight of calling `seal_random`. It includes the weight for copying the subject.
Random, Random,
/// Weight of calling `seal_deposit_event` with the given number of topics and event size. /// Weight of calling `seal_deposit_event` with the given number of topics and event size.
DepositEvent { num_topic: u32, len: u32 }, DepositEvent { num_topic: u32, len: u32 },
/// Weight of calling `seal_debug_message`. /// Weight of calling `seal_debug_message`.
DebugMessage, DebugMessage,
/// Weight of calling `seal_set_rent_allowance`.
SetRentAllowance,
/// Weight of calling `seal_set_storage` for the given storage item size. /// Weight of calling `seal_set_storage` for the given storage item size.
SetStorage(u32), SetStorage(u32),
/// Weight of calling `seal_clear_storage`. /// Weight of calling `seal_clear_storage`.
@@ -232,8 +224,7 @@ impl RuntimeCosts {
Balance => s.balance, Balance => s.balance,
ValueTransferred => s.value_transferred, ValueTransferred => s.value_transferred,
MinimumBalance => s.minimum_balance, MinimumBalance => s.minimum_balance,
TombstoneDeposit => s.tombstone_deposit, ContractDeposit => s.contract_deposit,
RentAllowance => s.rent_allowance,
BlockNumber => s.block_number, BlockNumber => s.block_number,
Now => s.now, Now => s.now,
WeightToFee => s.weight_to_fee, WeightToFee => s.weight_to_fee,
@@ -241,15 +232,12 @@ impl RuntimeCosts {
InputCopyOut(len) => s.input_per_byte.saturating_mul(len.into()), InputCopyOut(len) => s.input_per_byte.saturating_mul(len.into()),
Return(len) => s.r#return.saturating_add(s.return_per_byte.saturating_mul(len.into())), Return(len) => s.r#return.saturating_add(s.return_per_byte.saturating_mul(len.into())),
Terminate => s.terminate, Terminate => s.terminate,
RestoreTo(delta) =>
s.restore_to.saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())),
Random => s.random, Random => s.random,
DepositEvent { num_topic, len } => s DepositEvent { num_topic, len } => s
.deposit_event .deposit_event
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into())) .saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())), .saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
DebugMessage => s.debug_message, DebugMessage => s.debug_message,
SetRentAllowance => s.set_rent_allowance,
SetStorage(len) => SetStorage(len) =>
s.set_storage.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())), s.set_storage.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())),
ClearStorage => s.clear_storage, ClearStorage => s.clear_storage,
@@ -395,8 +383,6 @@ where
}, },
TrapReason::Termination => TrapReason::Termination =>
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }), Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
TrapReason::Restoration =>
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
TrapReason::SupervisorError(error) => Err(error)?, TrapReason::SupervisorError(error) => Err(error)?,
} }
} }
@@ -617,15 +603,13 @@ where
let not_funded = Error::<E::T>::NewContractNotFunded.into(); let not_funded = Error::<E::T>::NewContractNotFunded.into();
let no_code = Error::<E::T>::CodeNotFound.into(); let no_code = Error::<E::T>::CodeNotFound.into();
let not_found = Error::<E::T>::ContractNotFound.into(); let not_found = Error::<E::T>::ContractNotFound.into();
let is_tombstone = Error::<E::T>::ContractIsTombstone.into();
let rent_not_paid = Error::<E::T>::RentNotPaid.into();
match from { match from {
x if x == below_sub => Ok(BelowSubsistenceThreshold), x if x == below_sub => Ok(BelowSubsistenceThreshold),
x if x == transfer_failed => Ok(TransferFailed), x if x == transfer_failed => Ok(TransferFailed),
x if x == not_funded => Ok(NewContractNotFunded), x if x == not_funded => Ok(NewContractNotFunded),
x if x == no_code => Ok(CodeNotFound), x if x == no_code => Ok(CodeNotFound),
x if (x == not_found || x == is_tombstone || x == rent_not_paid) => Ok(NotCallable), x if x == not_found => Ok(NotCallable),
err => Err(err), err => Err(err),
} }
} }
@@ -737,49 +721,6 @@ where
self.ext.terminate(&beneficiary)?; self.ext.terminate(&beneficiary)?;
Err(TrapReason::Termination) Err(TrapReason::Termination)
} }
fn restore_to(
&mut self,
dest_ptr: u32,
code_hash_ptr: u32,
rent_allowance_ptr: u32,
delta_ptr: u32,
delta_count: u32,
) -> Result<(), TrapReason> {
self.charge_gas(RuntimeCosts::RestoreTo(delta_count))?;
let dest: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(dest_ptr)?;
let code_hash: CodeHash<<E as Ext>::T> = self.read_sandbox_memory_as(code_hash_ptr)?;
let rent_allowance: BalanceOf<<E as Ext>::T> =
self.read_sandbox_memory_as(rent_allowance_ptr)?;
let delta = {
const KEY_SIZE: usize = 32;
// We can eagerly allocate because we charged for the complete delta count already
// We still need to make sure that the allocation isn't larger than the memory
// allocator can handle.
let max_memory = self.ext.schedule().limits.max_memory_size();
ensure!(
delta_count.saturating_mul(KEY_SIZE as u32) <= max_memory,
Error::<E::T>::OutOfBounds,
);
let mut delta = vec![[0; KEY_SIZE]; delta_count as usize];
let mut key_ptr = delta_ptr;
for i in 0..delta_count {
// Read the delta into the provided buffer
// This cannot panic because of the loop condition
self.read_sandbox_memory_into_buf(key_ptr, &mut delta[i as usize])?;
// Offset key_ptr to the next element.
key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or(Error::<E::T>::OutOfBounds)?;
}
delta
};
self.ext.restore_to(dest, code_hash, rent_allowance, delta)?;
Err(TrapReason::Restoration)
}
} }
// *********************************************************** // ***********************************************************
@@ -1369,7 +1310,20 @@ define_env!(Env, <E: Ext>,
)?) )?)
}, },
// Stores the tombstone deposit into the supplied buffer. // Stores the contract deposit into the supplied buffer.
//
// # Deprecation
//
// This is equivalent to calling `seal_contract_deposit` and only exists for backwards
// compatibility. See that function for documentation.
[seal0] seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeCosts::ContractDeposit)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.contract_deposit().encode(), false, already_charged
)?)
},
// Stores the contract deposit into the supplied buffer.
// //
// The value is stored to linear memory at the address pointed to by `out_ptr`. // The value is stored to linear memory at the address pointed to by `out_ptr`.
// `out_len_ptr` must point to a u32 value that describes the available space at // `out_len_ptr` must point to a u32 value that describes the available space at
@@ -1380,95 +1334,53 @@ define_env!(Env, <E: Ext>,
// //
// # Note // # Note
// //
// The tombstone deposit is on top of the existential deposit. So in order for // The contract deposit is on top of the existential deposit. The sum
// a contract to leave a tombstone the balance of the contract must not go // is commonly referred as subsistence threshold in code. No contract initiated
// below the sum of existential deposit and the tombstone deposit. The sum // balance transfer can go below this threshold.
// is commonly referred as subsistence threshold in code. [seal0] seal_contract_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
[seal0] seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => { ctx.charge_gas(RuntimeCosts::ContractDeposit)?;
ctx.charge_gas(RuntimeCosts::TombstoneDeposit)?;
Ok(ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.contract_deposit().encode(), false, already_charged
)?) )?)
}, },
// Try to restore the given destination contract sacrificing the caller. // Was used to restore the given destination contract sacrificing the caller.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function. The newer version
// drops the now unnecessary length fields.
// //
// # Note // # Note
// //
// The values `_dest_len`, `_code_hash_len` and `_rent_allowance_len` are ignored because // The state rent functionality was removed. This is stub only exists for
// the encoded sizes of those types are fixed through `[`MaxEncodedLen`]. The fields // backwards compatiblity
// exist for backwards compatibility. Consider switching to the newest version of this function.
[seal0] seal_restore_to( [seal0] seal_restore_to(
ctx, ctx,
dest_ptr: u32, _dest_ptr: u32,
_dest_len: u32, _dest_len: u32,
code_hash_ptr: u32, _code_hash_ptr: u32,
_code_hash_len: u32, _code_hash_len: u32,
rent_allowance_ptr: u32, _rent_allowance_ptr: u32,
_rent_allowance_len: u32, _rent_allowance_len: u32,
delta_ptr: u32, _delta_ptr: u32,
delta_count: u32 _delta_count: u32
) => { ) => {
ctx.restore_to( ctx.charge_gas(RuntimeCosts::DebugMessage)?;
dest_ptr, Ok(())
code_hash_ptr,
rent_allowance_ptr,
delta_ptr,
delta_count,
)
}, },
// Try to restore the given destination contract sacrificing the caller. // Was used to restore the given destination contract sacrificing the caller.
// //
// This function will compute a tombstone hash from the caller's storage and the given code hash // # Note
// and if the hash matches the hash found in the tombstone at the specified address - kill
// the caller contract and restore the destination contract and set the specified `rent_allowance`.
// All caller's funds are transferred to the destination.
// //
// The tombstone hash is derived as `hash(code_hash, storage_root_hash)`. In order to match // The state rent functionality was removed. This is stub only exists for
// this hash to its own hash the restorer must make its storage equal to the one of the // backwards compatiblity
// evicted destination contract. In order to allow for additional storage items in the
// restoring contract a delta can be specified to this function. All keys specified as
// delta are disregarded when calculating the storage root hash.
//
// On success, the destination contract is restored. This function is diverging and
// stops execution even on success.
//
// - `dest_ptr` - the pointer to a buffer that encodes `T::AccountId`
// with the address of the to be restored contract.
// - `code_hash_ptr` - the pointer to a buffer that encodes
// a code hash of the to be restored contract.
// - `rent_allowance_ptr` - the pointer to a buffer that
// encodes the rent allowance that must be set in the case of successful restoration.
// - `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys
// laid out sequentially.
//
// # Traps
//
// - There is no tombstone at the destination address.
// - Tombstone hashes do not match.
// - The calling contract is already present on the call stack.
// - The supplied code_hash does not exist on-chain.
[seal1] seal_restore_to( [seal1] seal_restore_to(
ctx, ctx,
dest_ptr: u32, _dest_ptr: u32,
code_hash_ptr: u32, _code_hash_ptr: u32,
rent_allowance_ptr: u32, _rent_allowance_ptr: u32,
delta_ptr: u32, _delta_ptr: u32,
delta_count: u32 _delta_count: u32
) => { ) => {
ctx.restore_to( ctx.charge_gas(RuntimeCosts::DebugMessage)?;
dest_ptr, Ok(())
code_hash_ptr,
rent_allowance_ptr,
delta_ptr,
delta_count,
)
}, },
// Deposit a contract event with the data buffer and optional list of topics. There is a limit // Deposit a contract event with the data buffer and optional list of topics. There is a limit
@@ -1536,47 +1448,37 @@ define_env!(Env, <E: Ext>,
Ok(()) Ok(())
}, },
// Set rent allowance of the contract. // Was used to set rent allowance of the contract.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function. The newer version
// drops the now unnecessary length fields.
// //
// # Note // # Note
// //
// The value `_VALUE_len` is ignored because the encoded sizes // The state rent functionality was removed. This is stub only exists for
// this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards // backwards compatiblity.
// compatibility. Consider switching to the newest version of this function. [seal0] seal_set_rent_allowance(ctx, _value_ptr: u32, _value_len: u32) => {
[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, _value_len: u32) => { ctx.charge_gas(RuntimeCosts::DebugMessage)?;
ctx.charge_gas(RuntimeCosts::SetRentAllowance)?;
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr)?;
ctx.ext.set_rent_allowance(value);
Ok(()) Ok(())
}, },
// Set rent allowance of the contract. // Was used to set rent allowance of the contract.
// //
// - value_ptr: a pointer to the buffer with value, how much to allow for rent // # Note
// Should be decodable as a `T::Balance`. Traps otherwise. //
[seal1] seal_set_rent_allowance(ctx, value_ptr: u32) => { // The state rent functionality was removed. This is stub only exists for
ctx.charge_gas(RuntimeCosts::SetRentAllowance)?; // backwards compatiblity.
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr)?; [seal1] seal_set_rent_allowance(ctx, _value_ptr: u32) => {
ctx.ext.set_rent_allowance(value); ctx.charge_gas(RuntimeCosts::DebugMessage)?;
Ok(()) Ok(())
}, },
// Stores the rent allowance into the supplied buffer. // Was used to store the rent allowance into the supplied buffer.
// //
// The value is stored to linear memory at the address pointed to by `out_ptr`. // # Note
// `out_len_ptr` must point to a u32 value that describes the available space at
// `out_ptr`. This call overwrites it with the size of the value. If the available
// space at `out_ptr` is less than the size of the value a trap is triggered.
// //
// The data is encoded as T::Balance. // The state rent functionality was removed. This is stub only exists for
// backwards compatiblity.
[seal0] seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => { [seal0] seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeCosts::RentAllowance)?; ctx.charge_gas(RuntimeCosts::Balance)?;
let rent_allowance = ctx.ext.rent_allowance().encode(); let rent_allowance = <BalanceOf<E::T>>::max_value().encode();
Ok(ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &rent_allowance, false, already_charged out_ptr, out_len_ptr, &rent_allowance, false, already_charged
)?) )?)
@@ -1757,55 +1659,6 @@ define_env!(Env, <E: Ext>,
Ok(ReturnCode::LoggingDisabled) Ok(ReturnCode::LoggingDisabled)
}, },
// Stores the rent params into the supplied buffer.
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
// `out_len_ptr` must point to a u32 value that describes the available space at
// `out_ptr`. This call overwrites it with the size of the value. If the available
// space at `out_ptr` is less than the size of the value a trap is triggered.
//
// The data is encoded as [`crate::exec::RentParams`].
//
// # Note
//
// The returned information was collected and cached when the current contract call
// started execution. Any change to those values that happens due to actions of the
// current call or contracts that are called by this contract are not considered.
//
// # Unstable
//
// This function is unstable and subject to change (or removal) in the future. Do not
// deploy a contract using it to a production chain.
[__unstable__] seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
)?)
},
// Stores the rent status into the supplied buffer.
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
// `out_len_ptr` must point to a u32 value that describes the available space at
// `out_ptr`. This call overwrites it with the size of the value. If the available
// space at `out_ptr` is less than the size of the value a trap is triggered.
//
// The data is encoded as [`crate::rent::RentStatus`].
//
// # Parameters
//
// - `at_refcount`: The refcount assumed for the returned `custom_refcount_*` fields
//
// # Unstable
//
// This function is unstable and subject to change (or removal) in the future. Do not
// deploy a contract using it to a production chain.
[__unstable__] seal_rent_status(ctx, at_refcount: u32, out_ptr: u32, out_len_ptr: u32) => {
let rent_status = ctx.ext.rent_status(at_refcount).encode();
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &rent_status, false, already_charged
)?)
},
// Call some dispatchable of the runtime. // Call some dispatchable of the runtime.
// //
// This function decodes the passed in data as the overarching `Call` type of the // This function decodes the passed in data as the overarching `Call` type of the
File diff suppressed because it is too large Load Diff