mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 22:27:56 +00:00
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:
committed by
GitHub
parent
13f3e25ebb
commit
522e77e243
File diff suppressed because it is too large
Load Diff
@@ -16,11 +16,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
gas::GasMeter,
|
||||
rent::{Rent, RentStatus},
|
||||
storage::Storage,
|
||||
AccountCounter, AliveContractInfo, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf,
|
||||
Error, Event, Pallet as Contracts, Schedule,
|
||||
gas::GasMeter, storage::Storage, AccountCounter, BalanceOf, CodeHash, Config, ContractInfo,
|
||||
ContractInfoOf, Error, Event, Pallet as Contracts, Schedule,
|
||||
};
|
||||
use frame_support::{
|
||||
dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable},
|
||||
@@ -28,18 +25,19 @@ use frame_support::{
|
||||
storage::{with_transaction, TransactionOutcome},
|
||||
traits::{Contains, Currency, ExistenceRequirement, Get, OriginTrait, Randomness, Time},
|
||||
weights::Weight,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_contracts_primitives::ExecReturnValue;
|
||||
use smallvec::{Array, SmallVec};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::{
|
||||
traits::{Convert, Saturating},
|
||||
Perbill,
|
||||
};
|
||||
use sp_runtime::traits::{Convert, Saturating};
|
||||
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 MomentOf<T> = <<T as Config>::Time as Time>::Moment;
|
||||
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
|
||||
/// smart-contract is executed.
|
||||
///
|
||||
@@ -197,25 +134,6 @@ pub trait Ext: sealing::Sealed {
|
||||
/// call stack.
|
||||
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.
|
||||
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.
|
||||
fn minimum_balance(&self) -> BalanceOf<Self::T>;
|
||||
|
||||
/// Returns the deposit required to create a tombstone upon contract eviction.
|
||||
fn tombstone_deposit(&self) -> BalanceOf<Self::T>;
|
||||
/// Returns the deposit required to instantiate a contract.
|
||||
fn contract_deposit(&self) -> BalanceOf<Self::T>;
|
||||
|
||||
/// Returns a random number for the current block with the given subject.
|
||||
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`.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
///
|
||||
/// Returns the size of the original code.
|
||||
@@ -387,23 +290,6 @@ pub trait Executable<T: Config>: Sized {
|
||||
|
||||
// The number of contracts using this executable.
|
||||
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.
|
||||
@@ -461,8 +347,6 @@ pub struct Frame<T: Config> {
|
||||
contract_info: CachedContract<T>,
|
||||
/// The amount of balance transferred by the caller as part of the call.
|
||||
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.
|
||||
entry_point: ExportedFunction,
|
||||
/// 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.
|
||||
dest: T::AccountId,
|
||||
/// If `None` the contract info needs to be reloaded from storage.
|
||||
cached_info: Option<AliveContractInfo<T>>,
|
||||
cached_info: Option<ContractInfo<T>>,
|
||||
},
|
||||
Instantiate {
|
||||
/// 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`.
|
||||
enum CachedContract<T: Config> {
|
||||
/// 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.
|
||||
///
|
||||
/// In this case the cached contract is stale and needs to be reloaded from storage.
|
||||
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
|
||||
/// calls cannot remove a contract as this is checked and denied.
|
||||
@@ -510,13 +394,8 @@ enum CachedContract<T: Config> {
|
||||
|
||||
impl<T: Config> Frame<T> {
|
||||
/// Return the `contract_info` of the current contract.
|
||||
fn contract_info(&mut self) -> &mut AliveContractInfo<T> {
|
||||
self.contract_info.as_alive(&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)
|
||||
fn contract_info(&mut self) -> &mut ContractInfo<T> {
|
||||
self.contract_info.get(&self.account_id)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
@@ -540,7 +419,7 @@ macro_rules! get_cached_or_panic_after_load {
|
||||
} else {
|
||||
panic!(
|
||||
"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
|
||||
that is currently active on the call stack;\
|
||||
qed"
|
||||
@@ -553,28 +432,21 @@ impl<T: Config> CachedContract<T> {
|
||||
/// Load the `contract_info` from storage if necessary.
|
||||
fn load(&mut self, account_id: &T::AccountId) {
|
||||
if let CachedContract::Invalidated = self {
|
||||
let contract =
|
||||
<ContractInfoOf<T>>::get(&account_id).and_then(|contract| contract.get_alive());
|
||||
let contract = <ContractInfoOf<T>>::get(&account_id);
|
||||
if let Some(contract) = contract {
|
||||
*self = CachedContract::Cached(contract);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the cached contract_info as alive contract info.
|
||||
fn as_alive(&mut self, account_id: &T::AccountId) -> &mut AliveContractInfo<T> {
|
||||
/// Return the cached contract_info.
|
||||
fn get(&mut self, account_id: &T::AccountId) -> &mut ContractInfo<T> {
|
||||
self.load(account_id);
|
||||
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.
|
||||
fn terminate(&mut self, account_id: &T::AccountId) -> AliveContractInfo<T> {
|
||||
fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo<T> {
|
||||
self.load(account_id);
|
||||
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated))
|
||||
}
|
||||
@@ -695,23 +567,11 @@ where
|
||||
let contract = if let Some(contract) = cached_info {
|
||||
contract
|
||||
} else {
|
||||
<ContractInfoOf<T>>::get(&dest)
|
||||
.ok_or(<Error<T>>::ContractNotFound.into())
|
||||
.and_then(|contract| {
|
||||
contract.get_alive().ok_or(<Error<T>>::ContractIsTombstone)
|
||||
})?
|
||||
<ContractInfoOf<T>>::get(&dest).ok_or(<Error<T>>::ContractNotFound)?
|
||||
};
|
||||
|
||||
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)
|
||||
},
|
||||
FrameArgs::Instantiate { sender, trie_seed, executable, salt } => {
|
||||
@@ -728,12 +588,6 @@ where
|
||||
};
|
||||
|
||||
let frame = Frame {
|
||||
rent_params: RentParams::new(
|
||||
&account_id,
|
||||
&value_transferred,
|
||||
&contract_info,
|
||||
&executable,
|
||||
),
|
||||
value_transferred,
|
||||
contract_info: CachedContract::Cached(contract_info),
|
||||
account_id,
|
||||
@@ -756,18 +610,17 @@ where
|
||||
return Err(Error::<T>::MaxCallDepthReached.into())
|
||||
}
|
||||
|
||||
// We need to make sure that changes made to the contract info are not discarded.
|
||||
// See the `in_memory_changes_not_discarded` test for more information.
|
||||
// We do not store on instantiate because we do not allow to call into a contract
|
||||
// from its own constructor.
|
||||
let frame = self.top_frame();
|
||||
if let (CachedContract::Cached(contract), ExportedFunction::Call) =
|
||||
(&frame.contract_info, frame.entry_point)
|
||||
{
|
||||
<ContractInfoOf<T>>::insert(
|
||||
frame.account_id.clone(),
|
||||
ContractInfo::Alive(contract.clone()),
|
||||
);
|
||||
if CONTRACT_INFO_CAN_CHANGE {
|
||||
// We need to make sure that changes made to the contract info are not discarded.
|
||||
// See the `in_memory_changes_not_discarded` test for more information.
|
||||
// We do not store on instantiate because we do not allow to call into a contract
|
||||
// from its own constructor.
|
||||
let frame = self.top_frame();
|
||||
if let (CachedContract::Cached(contract), ExportedFunction::Call) =
|
||||
(&frame.contract_info, frame.entry_point)
|
||||
{
|
||||
<ContractInfoOf<T>>::insert(frame.account_id.clone(), contract.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let nested_meter =
|
||||
@@ -784,12 +637,6 @@ where
|
||||
fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<ExecReturnValue, ExecError> {
|
||||
let entry_point = self.top_frame().entry_point;
|
||||
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.
|
||||
self.initial_transfer()?;
|
||||
|
||||
@@ -808,16 +655,6 @@ where
|
||||
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_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
|
||||
// cache needs to be invalidated because that one will invalidate the next cache
|
||||
// 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) {
|
||||
c.contract_info = CachedContract::Invalidated;
|
||||
}
|
||||
@@ -897,10 +734,7 @@ where
|
||||
return
|
||||
}
|
||||
if let CachedContract::Cached(contract) = &self.first_frame.contract_info {
|
||||
<ContractInfoOf<T>>::insert(
|
||||
&self.first_frame.account_id,
|
||||
ContractInfo::Alive(contract.clone()),
|
||||
);
|
||||
<ContractInfoOf<T>>::insert(&self.first_frame.account_id, contract.clone());
|
||||
}
|
||||
if let Some(counter) = self.account_counter {
|
||||
<AccountCounter<T>>::set(counter);
|
||||
@@ -1111,38 +945,6 @@ where
|
||||
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 {
|
||||
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 {
|
||||
let block_number = self.block_number;
|
||||
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 {
|
||||
@@ -1185,8 +986,8 @@ where
|
||||
T::Currency::minimum_balance()
|
||||
}
|
||||
|
||||
fn tombstone_deposit(&self) -> BalanceOf<T> {
|
||||
T::TombstoneDeposit::get()
|
||||
fn contract_deposit(&self) -> BalanceOf<T> {
|
||||
T::ContractDeposit::get()
|
||||
}
|
||||
|
||||
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 {
|
||||
self.block_number
|
||||
}
|
||||
@@ -1220,24 +1013,6 @@ where
|
||||
&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> {
|
||||
&mut self.top_frame_mut().nested_meter
|
||||
}
|
||||
@@ -1304,7 +1079,7 @@ mod tests {
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use frame_system::{EventRecord, Phase};
|
||||
use pallet_contracts_primitives::ReturnFlags;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use pretty_assertions::assert_eq;
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::{
|
||||
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 {
|
||||
@@ -1428,10 +1197,6 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
fn drop_from_storage(self) {
|
||||
MockLoader::decrement_refcount(self.code_hash);
|
||||
}
|
||||
|
||||
fn add_user(
|
||||
code_hash: CodeHash<Test>,
|
||||
_: &mut GasMeter<Test>,
|
||||
@@ -1546,7 +1311,7 @@ mod tests {
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
place_contract(&BOB, return_ch);
|
||||
place_contract(&dest, return_ch);
|
||||
set_balance(&origin, 100);
|
||||
let balance = get_balance(&dest);
|
||||
|
||||
@@ -1563,9 +1328,7 @@ mod tests {
|
||||
|
||||
assert!(!output.is_success());
|
||||
assert_eq!(get_balance(&origin), 100);
|
||||
|
||||
// the rent is still charged
|
||||
assert!(get_balance(&dest) < balance);
|
||||
assert_eq!(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]
|
||||
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)
|
||||
// This tests verfies some edge case of the contract info cache:
|
||||
// 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.
|
||||
let code_bob = MockLoader::insert(Call, |ctx, _| {
|
||||
if ctx.input_data[0] == 0 {
|
||||
let original_allowance = ctx.ext.rent_allowance();
|
||||
let changed_allowance = <BalanceOf<Test>>::max_value() / 2;
|
||||
assert_ne!(original_allowance, changed_allowance);
|
||||
ctx.ext.set_rent_allowance(changed_allowance);
|
||||
// DO: modify medata (ContractInfo) of own contract through ctx.ext functions
|
||||
assert_eq!(ctx.ext.call(0, CHARLIE, 0, vec![], true), exec_trapped());
|
||||
assert_eq!(ctx.ext.rent_allowance(), changed_allowance);
|
||||
assert_ne!(ctx.ext.rent_allowance(), original_allowance);
|
||||
// DO: check that the value is not discarded (query via ctx.ext)
|
||||
}
|
||||
exec_success()
|
||||
});
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
//! * [`Pallet::instantiate`] - The same as `instantiate_with_code` but instead of uploading new
|
||||
//! code an existing `code_hash` is supplied.
|
||||
//! * [`Pallet::call`] - Makes a call to an account, optionally transferring some balance.
|
||||
//! * [`Pallet::claim_surcharge`] - Evict a contract that cannot pay rent anymore.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
@@ -89,7 +88,6 @@ mod gas;
|
||||
mod benchmarking;
|
||||
mod exec;
|
||||
mod migration;
|
||||
mod rent;
|
||||
mod schedule;
|
||||
mod storage;
|
||||
mod wasm;
|
||||
@@ -108,38 +106,31 @@ pub use crate::{
|
||||
use crate::{
|
||||
exec::{Executable, Stack as ExecStack},
|
||||
gas::GasMeter,
|
||||
rent::Rent,
|
||||
storage::{AliveContractInfo, ContractInfo, DeletedContract, Storage, TombstoneContractInfo},
|
||||
storage::{ContractInfo, DeletedContract, Storage},
|
||||
wasm::PrefabWasmModule,
|
||||
weights::WeightInfo,
|
||||
};
|
||||
use frame_support::{
|
||||
dispatch::Dispatchable,
|
||||
traits::{Contains, Currency, Get, OnUnbalanced, Randomness, StorageVersion, Time},
|
||||
weights::{GetDispatchInfo, PostDispatchInfo, Weight, WithPostDispatchInfo},
|
||||
traits::{Contains, Currency, Get, Randomness, StorageVersion, Time},
|
||||
weights::{GetDispatchInfo, PostDispatchInfo, Weight},
|
||||
};
|
||||
use frame_system::Pallet as System;
|
||||
use pallet_contracts_primitives::{
|
||||
Code, ContractAccessError, ContractExecResult, ContractInstantiateResult, GetStorageResult,
|
||||
InstantiateReturnValue, RentProjectionResult,
|
||||
InstantiateReturnValue,
|
||||
};
|
||||
use sp_core::{crypto::UncheckedFrom, Bytes};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, Hash, Saturating, StaticLookup, Zero},
|
||||
Perbill,
|
||||
};
|
||||
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
type CodeHash<T> = <T as frame_system::Config>::Hash;
|
||||
type TrieId = Vec<u8>;
|
||||
type BalanceOf<T> =
|
||||
<<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.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
@@ -177,8 +168,7 @@ pub mod pallet {
|
||||
///
|
||||
/// The runtime **must** make sure that any allowed dispatchable makes sure that the
|
||||
/// `total_balance` of the contract stays above [`Pallet::subsistence_threshold()`].
|
||||
/// Otherwise contracts can clutter the storage with their tombstones without
|
||||
/// deposting the correct amount of balance.
|
||||
/// Otherwise users could clutter the storage with contracts.
|
||||
///
|
||||
/// # Stability
|
||||
///
|
||||
@@ -195,9 +185,6 @@ pub mod pallet {
|
||||
/// be exploited to drive the runtime into a panic.
|
||||
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 calculate the actual fee and is only for informational purposes.
|
||||
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
|
||||
@@ -213,56 +200,12 @@ pub mod pallet {
|
||||
#[pallet::constant]
|
||||
type Schedule: Get<Schedule<Self>>;
|
||||
|
||||
/// Number of block delay an extrinsic claim surcharge has.
|
||||
///
|
||||
/// When claim surcharge is called by an extrinsic the rent is checked
|
||||
/// for current_block - delay
|
||||
/// The deposit that must be placed into the contract's account to instantiate it.
|
||||
/// This is in **addition** to the [`pallet_balances::Pallet::ExistenialDeposit`].
|
||||
/// The minimum balance for a contract's account can be queried using
|
||||
/// [`Pallet::subsistence_threshold`].
|
||||
#[pallet::constant]
|
||||
type SignedClaimHandicap: Get<Self::BlockNumber>;
|
||||
|
||||
/// 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>>;
|
||||
type ContractDeposit: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The type of the call stack determines the maximum nesting depth of contract calls.
|
||||
///
|
||||
@@ -439,50 +382,6 @@ pub mod pallet {
|
||||
gas_meter
|
||||
.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]
|
||||
@@ -492,10 +391,7 @@ pub mod pallet {
|
||||
/// Contract deployed by address at the specified address. \[deployer, contract\]
|
||||
Instantiated(T::AccountId, T::AccountId),
|
||||
|
||||
/// Contract has been evicted and is now in tombstone state. \[contract\]
|
||||
Evicted(T::AccountId),
|
||||
|
||||
/// Contract has been terminated without leaving a tombstone.
|
||||
/// Contract has been removed.
|
||||
/// \[contract, beneficiary\]
|
||||
///
|
||||
/// # Params
|
||||
@@ -505,21 +401,10 @@ pub mod pallet {
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The only way for a contract to be removed without a tombstone and emitting
|
||||
/// this event is by calling `seal_terminate`.
|
||||
/// The only way for a contract to be removed and emitting this event is by calling
|
||||
/// `seal_terminate`.
|
||||
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\]
|
||||
CodeStored(T::Hash),
|
||||
|
||||
@@ -544,7 +429,7 @@ pub mod pallet {
|
||||
/// A code with the specified hash was removed.
|
||||
/// \[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),
|
||||
}
|
||||
|
||||
@@ -552,24 +437,13 @@ pub mod pallet {
|
||||
pub enum Error<T> {
|
||||
/// A new schedule must have a greater version than the current one.
|
||||
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.
|
||||
OutOfGas,
|
||||
/// The output buffer supplied to a contract API call was too small.
|
||||
OutputBufferTooSmall,
|
||||
/// Performing the requested transfer would have brought the contract below
|
||||
/// the subsistence threshold. No transfer is allowed to do this in order to allow
|
||||
/// for a tombstone to be created. Use `seal_terminate` to remove a contract without
|
||||
/// leaving a tombstone behind.
|
||||
/// the subsistence threshold. No transfer is allowed to do this. Use `seal_terminate`
|
||||
/// to recover a deposit.
|
||||
BelowSubsistenceThreshold,
|
||||
/// The newly created contract is below the subsistence threshold after executing
|
||||
/// its contructor. No contracts are allowed to exist below that threshold.
|
||||
@@ -583,18 +457,6 @@ pub mod pallet {
|
||||
MaxCallDepthReached,
|
||||
/// No contract was found at the specified address.
|
||||
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
|
||||
/// current schedule.
|
||||
CodeTooLarge,
|
||||
@@ -609,7 +471,7 @@ pub mod pallet {
|
||||
/// The size defined in `T::MaxValueSize` was exceeded.
|
||||
ValueTooLarge,
|
||||
/// 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,
|
||||
/// `seal_call` forwarded this contracts input. It therefore is no longer available.
|
||||
InputForwarded,
|
||||
@@ -625,15 +487,10 @@ pub mod pallet {
|
||||
NoChainExtension,
|
||||
/// 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.
|
||||
/// Trying again during another block is the only way to resolve this issue.
|
||||
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.
|
||||
///
|
||||
/// This can either happen when the accumulated storage in bytes is too large or
|
||||
@@ -643,7 +500,7 @@ pub mod pallet {
|
||||
DuplicateContract,
|
||||
/// 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,
|
||||
/// The debug message specified to `seal_debug_message` does contain invalid UTF-8.
|
||||
DebugMessageInvalidUTF8,
|
||||
@@ -730,9 +587,6 @@ where
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
@@ -746,9 +600,8 @@ where
|
||||
code: Code<CodeHash<T>>,
|
||||
data: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
compute_projection: bool,
|
||||
debug: bool,
|
||||
) -> ContractInstantiateResult<T::AccountId, T::BlockNumber> {
|
||||
) -> ContractInstantiateResult<T::AccountId> {
|
||||
let mut gas_meter = GasMeter::new(gas_limit);
|
||||
let schedule = T::Schedule::get();
|
||||
let executable = match code {
|
||||
@@ -776,18 +629,7 @@ where
|
||||
&salt,
|
||||
debug_message.as_mut(),
|
||||
)
|
||||
.and_then(|(account_id, result)| {
|
||||
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 })
|
||||
});
|
||||
.and_then(|(account_id, result)| Ok(InstantiateReturnValue { result, account_id }));
|
||||
ContractInstantiateResult {
|
||||
result: result.map_err(|e| e.error),
|
||||
gas_consumed: gas_meter.gas_consumed(),
|
||||
@@ -798,21 +640,13 @@ where
|
||||
|
||||
/// Query storage of a specified contract under a specified key.
|
||||
pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult {
|
||||
let contract_info = ContractInfoOf::<T>::get(&address)
|
||||
.ok_or(ContractAccessError::DoesntExist)?
|
||||
.get_alive()
|
||||
.ok_or(ContractAccessError::IsTombstone)?;
|
||||
let contract_info =
|
||||
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
|
||||
|
||||
let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key);
|
||||
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,
|
||||
///
|
||||
/// 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)
|
||||
/// 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
|
||||
/// than the subsistence threshold in order to guarantee that a tombstone is created.
|
||||
///
|
||||
/// The only way to completely kill a contract without a tombstone is calling `seal_terminate`.
|
||||
/// Any contract initiated balance transfer mechanism cannot make the balance lower
|
||||
/// than the subsistence threshold. The only way to recover the balance is to remove
|
||||
/// contract using `seal_terminate`.
|
||||
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.
|
||||
|
||||
@@ -18,18 +18,104 @@
|
||||
use crate::{Config, Pallet, Weight};
|
||||
use frame_support::{
|
||||
storage::migration,
|
||||
traits::{Get, PalletInfoAccess, StorageVersion},
|
||||
traits::{Get, PalletInfoAccess},
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub fn migrate<T: Config>() -> Weight {
|
||||
use frame_support::traits::StorageVersion;
|
||||
|
||||
let version = StorageVersion::get::<Pallet<T>>();
|
||||
let mut weight: Weight = 0;
|
||||
|
||||
if StorageVersion::get::<Pallet<T>>() == 3 {
|
||||
weight = weight.saturating_add(T::DbWeight::get().writes(1));
|
||||
migration::remove_storage_prefix(<Pallet<T>>::name().as_bytes(), b"CurrentSchedule", b"");
|
||||
|
||||
if version < 4 {
|
||||
weight = weight.saturating_add(v4::migrate::<T>());
|
||||
StorageVersion::new(4).put::<Pallet<T>>();
|
||||
}
|
||||
|
||||
if version < 5 {
|
||||
weight = weight.saturating_add(v5::migrate::<T>());
|
||||
StorageVersion::new(5).put::<Pallet<T>>();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> },
|
||||
}
|
||||
@@ -267,11 +267,8 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_minimum_balance`.
|
||||
pub minimum_balance: Weight,
|
||||
|
||||
/// Weight of calling `seal_tombstone_deposit`.
|
||||
pub tombstone_deposit: Weight,
|
||||
|
||||
/// Weight of calling `seal_rent_allowance`.
|
||||
pub rent_allowance: Weight,
|
||||
/// Weight of calling `seal_contract_deposit`.
|
||||
pub contract_deposit: Weight,
|
||||
|
||||
/// Weight of calling `seal_block_number`.
|
||||
pub block_number: Weight,
|
||||
@@ -300,12 +297,6 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_terminate`.
|
||||
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`.
|
||||
pub random: Weight,
|
||||
|
||||
@@ -321,9 +312,6 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_debug_message`.
|
||||
pub debug_message: Weight,
|
||||
|
||||
/// Weight of calling `seal_set_rent_allowance`.
|
||||
pub set_rent_allowance: Weight,
|
||||
|
||||
/// Weight of calling `seal_set_storage`.
|
||||
pub set_storage: Weight,
|
||||
|
||||
@@ -566,8 +554,7 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
balance: cost_batched!(seal_balance),
|
||||
value_transferred: cost_batched!(seal_value_transferred),
|
||||
minimum_balance: cost_batched!(seal_minimum_balance),
|
||||
tombstone_deposit: cost_batched!(seal_tombstone_deposit),
|
||||
rent_allowance: cost_batched!(seal_rent_allowance),
|
||||
contract_deposit: cost_batched!(seal_tombstone_deposit),
|
||||
block_number: cost_batched!(seal_block_number),
|
||||
now: cost_batched!(seal_now),
|
||||
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),
|
||||
return_per_byte: cost_byte!(seal_return_per_kb),
|
||||
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),
|
||||
deposit_event: cost_batched!(seal_deposit_event),
|
||||
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
|
||||
),
|
||||
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_per_byte: cost_byte_batched!(seal_set_storage_per_kb),
|
||||
clear_storage: cost_batched!(seal_clear_storage),
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
use crate::{
|
||||
exec::{AccountIdOf, StorageKey},
|
||||
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::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
storage::child::{self, ChildInfo, KillStorageResult},
|
||||
@@ -31,87 +31,26 @@ use frame_support::{
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, Hash, MaybeSerializeDeserialize, Member, Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
||||
use sp_runtime::{traits::Hash, RuntimeDebug};
|
||||
use sp_std::{marker::PhantomData, prelude::*};
|
||||
|
||||
pub type AliveContractInfo<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
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type ContractInfo<T> = RawContractInfo<CodeHash<T>>;
|
||||
|
||||
/// Information for managing an account and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account.
|
||||
#[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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
#[cfg(test)]
|
||||
pub fn child_trie_info(&self) -> ChildInfo {
|
||||
child_trie_info(&self.trie_id[..])
|
||||
}
|
||||
@@ -122,40 +61,9 @@ fn child_trie_info(trie_id: &[u8]) -> ChildInfo {
|
||||
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)]
|
||||
pub struct DeletedContract {
|
||||
pair_count: u32,
|
||||
trie_id: TrieId,
|
||||
pub(crate) trie_id: TrieId,
|
||||
}
|
||||
|
||||
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
|
||||
/// `read`, this function also requires the `account` ID.
|
||||
pub fn write(
|
||||
block_number: T::BlockNumber,
|
||||
new_info: &mut AliveContractInfo<T>,
|
||||
new_info: &mut ContractInfo<T>,
|
||||
key: &StorageKey,
|
||||
opt_new_value: Option<Vec<u8>>,
|
||||
) -> DispatchResult {
|
||||
let hashed_key = blake2_256(key);
|
||||
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 {
|
||||
Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]),
|
||||
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
|
||||
/// 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(
|
||||
account: &AccountIdOf<T>,
|
||||
trie_id: TrieId,
|
||||
ch: CodeHash<T>,
|
||||
) -> Result<AliveContractInfo<T>, DispatchError> {
|
||||
) -> Result<ContractInfo<T>, DispatchError> {
|
||||
if <ContractInfoOf<T>>::contains_key(account) {
|
||||
return Err(Error::<T>::DuplicateContract.into())
|
||||
}
|
||||
|
||||
let contract = AliveContractInfo::<T> {
|
||||
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,
|
||||
};
|
||||
let contract = ContractInfo::<T> { code_hash: ch, trie_id, _reserved: None };
|
||||
|
||||
Ok(contract)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// when queuing the trie for deletion.
|
||||
pub fn queue_trie_for_deletion(contract: &AliveContractInfo<T>) -> DispatchResult {
|
||||
/// You must make sure that the contract is also removed when queuing the trie for deletion.
|
||||
pub fn queue_trie_for_deletion(contract: &ContractInfo<T>) -> DispatchResult {
|
||||
if <DeletionQueue<T>>::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize {
|
||||
Err(Error::<T>::DeletionQueueFull.into())
|
||||
} else {
|
||||
<DeletionQueue<T>>::append(DeletedContract {
|
||||
pair_count: contract.pair_count,
|
||||
trie_id: contract.trie_id.clone(),
|
||||
});
|
||||
<DeletionQueue<T>>::append(DeletedContract { trie_id: contract.trie_id.clone() });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -302,12 +158,11 @@ where
|
||||
|
||||
/// 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
|
||||
/// apart from the base weight.
|
||||
/// It returns the amount of weight used for that task.
|
||||
pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight {
|
||||
let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0);
|
||||
if queue_len == 0 {
|
||||
return weight_limit
|
||||
return 0
|
||||
}
|
||||
|
||||
let (weight_per_key, mut remaining_key_budget) =
|
||||
@@ -322,33 +177,20 @@ where
|
||||
|
||||
let mut queue = <DeletionQueue<T>>::get();
|
||||
|
||||
while !queue.is_empty() && remaining_key_budget > 0 {
|
||||
// Cannot panic due to loop condition
|
||||
let trie = &mut queue[0];
|
||||
let pair_count = trie.pair_count;
|
||||
if let (Some(trie), true) = (queue.get(0), remaining_key_budget > 0) {
|
||||
let outcome =
|
||||
child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget));
|
||||
if pair_count > remaining_key_budget {
|
||||
// Cannot underflow because of the if condition
|
||||
trie.pair_count -= remaining_key_budget;
|
||||
} else {
|
||||
// We do not care to preserve order. The contract is deleted already and
|
||||
// noone waits for the trie to be deleted.
|
||||
let removed = queue.swap_remove(0);
|
||||
match outcome {
|
||||
// This should not happen as our budget was large enough to remove all keys.
|
||||
KillStorageResult::SomeRemaining(_) => {
|
||||
log::error!(
|
||||
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));
|
||||
let keys_removed = match outcome {
|
||||
// This should not happen as our budget was large enough to remove all keys.
|
||||
KillStorageResult::SomeRemaining(count) => count,
|
||||
KillStorageResult::AllRemoved(count) => {
|
||||
// We do not care to preserve order. The contract is deleted already and
|
||||
// noone waits for the trie to be deleted.
|
||||
queue.swap_remove(0);
|
||||
count
|
||||
},
|
||||
};
|
||||
remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed);
|
||||
}
|
||||
|
||||
<DeletionQueue<T>>::put(queue);
|
||||
@@ -365,14 +207,14 @@ where
|
||||
/// Returns the code hash of the contract specified by `account` ID.
|
||||
#[cfg(test)]
|
||||
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.
|
||||
#[cfg(test)]
|
||||
pub fn fill_queue_with_dummies() {
|
||||
let queue: Vec<_> = (0..T::DeletionQueueDepth::get())
|
||||
.map(|_| DeletedContract { pair_count: 0, trie_id: vec![] })
|
||||
.map(|_| DeletedContract { trie_id: vec![] })
|
||||
.collect();
|
||||
<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.
|
||||
pub fn increment_refcount<T: Config>(
|
||||
code_hash: CodeHash<T>,
|
||||
|
||||
@@ -61,7 +61,7 @@ pub struct PrefabWasmModule<T: Config> {
|
||||
/// The maximum memory size of a contract's sandbox.
|
||||
#[codec(compact)]
|
||||
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.
|
||||
#[codec(compact)]
|
||||
@@ -164,10 +164,6 @@ where
|
||||
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> {
|
||||
code_cache::increment_refcount::<T>(code_hash, gas_meter)
|
||||
}
|
||||
@@ -240,11 +236,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
exec::{
|
||||
AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, RentParams,
|
||||
SeedOf, StorageKey,
|
||||
AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, SeedOf, StorageKey,
|
||||
},
|
||||
gas::GasMeter,
|
||||
rent::RentStatus,
|
||||
tests::{Call, Test, ALICE, BOB},
|
||||
BalanceOf, CodeHash, Error, Pallet as Contracts,
|
||||
};
|
||||
@@ -261,14 +255,6 @@ mod tests {
|
||||
use sp_runtime::DispatchError;
|
||||
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)]
|
||||
struct InstantiateEntry {
|
||||
code_hash: H256,
|
||||
@@ -299,17 +285,14 @@ mod tests {
|
||||
|
||||
pub struct MockExt {
|
||||
storage: HashMap<StorageKey, Vec<u8>>,
|
||||
rent_allowance: u64,
|
||||
instantiates: Vec<InstantiateEntry>,
|
||||
terminations: Vec<TerminationEntry>,
|
||||
calls: Vec<CallEntry>,
|
||||
transfers: Vec<TransferEntry>,
|
||||
restores: Vec<RestoreEntry>,
|
||||
// (topics, data)
|
||||
events: Vec<(Vec<H256>, Vec<u8>)>,
|
||||
runtime_calls: RefCell<Vec<Call>>,
|
||||
schedule: Schedule<Test>,
|
||||
rent_params: RentParams<Test>,
|
||||
gas_meter: GasMeter<Test>,
|
||||
debug_buffer: Vec<u8>,
|
||||
}
|
||||
@@ -323,16 +306,13 @@ mod tests {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
storage: Default::default(),
|
||||
rent_allowance: Default::default(),
|
||||
instantiates: Default::default(),
|
||||
terminations: Default::default(),
|
||||
calls: Default::default(),
|
||||
transfers: Default::default(),
|
||||
restores: Default::default(),
|
||||
events: Default::default(),
|
||||
runtime_calls: Default::default(),
|
||||
schedule: Default::default(),
|
||||
rent_params: Default::default(),
|
||||
gas_meter: GasMeter::new(10_000_000_000),
|
||||
debug_buffer: Default::default(),
|
||||
}
|
||||
@@ -381,16 +361,6 @@ mod tests {
|
||||
self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() });
|
||||
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>> {
|
||||
self.storage.get(key).cloned()
|
||||
}
|
||||
@@ -416,7 +386,7 @@ mod tests {
|
||||
fn minimum_balance(&self) -> u64 {
|
||||
666
|
||||
}
|
||||
fn tombstone_deposit(&self) -> u64 {
|
||||
fn contract_deposit(&self) -> u64 {
|
||||
16
|
||||
}
|
||||
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>) {
|
||||
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 {
|
||||
121
|
||||
}
|
||||
@@ -443,12 +407,6 @@ mod tests {
|
||||
fn schedule(&self) -> &Schedule<Self::T> {
|
||||
&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> {
|
||||
&mut self.gas_meter
|
||||
}
|
||||
@@ -1380,9 +1338,9 @@ mod tests {
|
||||
assert_ok!(execute(CODE_MINIMUM_BALANCE, vec![], MockExt::default()));
|
||||
}
|
||||
|
||||
const CODE_TOMBSTONE_DEPOSIT: &str = r#"
|
||||
const CODE_CONTRACT_DEPOSIT: &str = r#"
|
||||
(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))
|
||||
|
||||
;; size of our buffer is 32 bytes
|
||||
@@ -1398,7 +1356,7 @@ mod tests {
|
||||
)
|
||||
|
||||
(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
|
||||
(call $assert
|
||||
@@ -1421,8 +1379,8 @@ mod tests {
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn tombstone_deposit() {
|
||||
assert_ok!(execute(CODE_TOMBSTONE_DEPOSIT, vec![], MockExt::default()));
|
||||
fn contract_deposit() {
|
||||
assert_ok!(execute(CODE_CONTRACT_DEPOSIT, vec![], MockExt::default()));
|
||||
}
|
||||
|
||||
const CODE_RANDOM: &str = r#"
|
||||
@@ -1856,81 +1814,6 @@ mod tests {
|
||||
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]
|
||||
fn debug_message_works() {
|
||||
const CODE_DEBUG_MESSAGE: &str = r#"
|
||||
|
||||
@@ -31,6 +31,7 @@ use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
||||
use pwasm_utils::parity_wasm::elements::ValueType;
|
||||
use sp_core::{crypto::UncheckedFrom, Bytes};
|
||||
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
|
||||
use sp_runtime::traits::Bounded;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// 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,
|
||||
/// No code could be found at the supplied code hash.
|
||||
CodeNotFound = 7,
|
||||
/// The contract that was called is either no contract at all (a plain account)
|
||||
/// or is a tombstone.
|
||||
/// The contract that was called is no contract (a plain account).
|
||||
NotCallable = 8,
|
||||
/// The call to `seal_debug_message` had no effect because debug message
|
||||
/// recording was disabled.
|
||||
@@ -121,8 +121,6 @@ pub enum TrapReason {
|
||||
/// Signals that a trap was generated in response to a successful call to the
|
||||
/// `seal_terminate` host function.
|
||||
Termination,
|
||||
/// Signals that a trap was generated because of a successful restoration.
|
||||
Restoration,
|
||||
}
|
||||
|
||||
impl<T: Into<DispatchError>> From<T> for TrapReason {
|
||||
@@ -149,10 +147,8 @@ pub enum RuntimeCosts {
|
||||
ValueTransferred,
|
||||
/// Weight of calling `seal_minimum_balance`.
|
||||
MinimumBalance,
|
||||
/// Weight of calling `seal_tombstone_deposit`.
|
||||
TombstoneDeposit,
|
||||
/// Weight of calling `seal_rent_allowance`.
|
||||
RentAllowance,
|
||||
/// Weight of calling `seal_contract_deposit`.
|
||||
ContractDeposit,
|
||||
/// Weight of calling `seal_block_number`.
|
||||
BlockNumber,
|
||||
/// Weight of calling `seal_now`.
|
||||
@@ -167,16 +163,12 @@ pub enum RuntimeCosts {
|
||||
Return(u32),
|
||||
/// Weight of calling `seal_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.
|
||||
Random,
|
||||
/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
|
||||
DepositEvent { num_topic: u32, len: u32 },
|
||||
/// Weight of calling `seal_debug_message`.
|
||||
DebugMessage,
|
||||
/// Weight of calling `seal_set_rent_allowance`.
|
||||
SetRentAllowance,
|
||||
/// Weight of calling `seal_set_storage` for the given storage item size.
|
||||
SetStorage(u32),
|
||||
/// Weight of calling `seal_clear_storage`.
|
||||
@@ -232,8 +224,7 @@ impl RuntimeCosts {
|
||||
Balance => s.balance,
|
||||
ValueTransferred => s.value_transferred,
|
||||
MinimumBalance => s.minimum_balance,
|
||||
TombstoneDeposit => s.tombstone_deposit,
|
||||
RentAllowance => s.rent_allowance,
|
||||
ContractDeposit => s.contract_deposit,
|
||||
BlockNumber => s.block_number,
|
||||
Now => s.now,
|
||||
WeightToFee => s.weight_to_fee,
|
||||
@@ -241,15 +232,12 @@ impl RuntimeCosts {
|
||||
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())),
|
||||
Terminate => s.terminate,
|
||||
RestoreTo(delta) =>
|
||||
s.restore_to.saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())),
|
||||
Random => s.random,
|
||||
DepositEvent { num_topic, len } => s
|
||||
.deposit_event
|
||||
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
|
||||
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
|
||||
DebugMessage => s.debug_message,
|
||||
SetRentAllowance => s.set_rent_allowance,
|
||||
SetStorage(len) =>
|
||||
s.set_storage.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())),
|
||||
ClearStorage => s.clear_storage,
|
||||
@@ -395,8 +383,6 @@ where
|
||||
},
|
||||
TrapReason::Termination =>
|
||||
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)?,
|
||||
}
|
||||
}
|
||||
@@ -617,15 +603,13 @@ where
|
||||
let not_funded = Error::<E::T>::NewContractNotFunded.into();
|
||||
let no_code = Error::<E::T>::CodeNotFound.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 {
|
||||
x if x == below_sub => Ok(BelowSubsistenceThreshold),
|
||||
x if x == transfer_failed => Ok(TransferFailed),
|
||||
x if x == not_funded => Ok(NewContractNotFunded),
|
||||
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),
|
||||
}
|
||||
}
|
||||
@@ -737,49 +721,6 @@ where
|
||||
self.ext.terminate(&beneficiary)?;
|
||||
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`.
|
||||
// `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
|
||||
//
|
||||
// The tombstone deposit is on top of the existential deposit. So in order for
|
||||
// a contract to leave a tombstone the balance of the contract must not go
|
||||
// below the sum of existential deposit and the tombstone deposit. The sum
|
||||
// is commonly referred as subsistence threshold in code.
|
||||
[seal0] seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
ctx.charge_gas(RuntimeCosts::TombstoneDeposit)?;
|
||||
// The contract deposit is on top of the existential deposit. The sum
|
||||
// is commonly referred as subsistence threshold in code. No contract initiated
|
||||
// balance transfer can go below this threshold.
|
||||
[seal0] seal_contract_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.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.
|
||||
//
|
||||
// # Deprecation
|
||||
//
|
||||
// This is equivalent to calling the newer version of this function. The newer version
|
||||
// drops the now unnecessary length fields.
|
||||
// Was used to restore the given destination contract sacrificing the caller.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The values `_dest_len`, `_code_hash_len` and `_rent_allowance_len` are ignored because
|
||||
// the encoded sizes of those types are fixed through `[`MaxEncodedLen`]. The fields
|
||||
// exist for backwards compatibility. Consider switching to the newest version of this function.
|
||||
// The state rent functionality was removed. This is stub only exists for
|
||||
// backwards compatiblity
|
||||
[seal0] seal_restore_to(
|
||||
ctx,
|
||||
dest_ptr: u32,
|
||||
_dest_ptr: u32,
|
||||
_dest_len: u32,
|
||||
code_hash_ptr: u32,
|
||||
_code_hash_ptr: u32,
|
||||
_code_hash_len: u32,
|
||||
rent_allowance_ptr: u32,
|
||||
_rent_allowance_ptr: u32,
|
||||
_rent_allowance_len: u32,
|
||||
delta_ptr: u32,
|
||||
delta_count: u32
|
||||
_delta_ptr: u32,
|
||||
_delta_count: u32
|
||||
) => {
|
||||
ctx.restore_to(
|
||||
dest_ptr,
|
||||
code_hash_ptr,
|
||||
rent_allowance_ptr,
|
||||
delta_ptr,
|
||||
delta_count,
|
||||
)
|
||||
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
// # Note
|
||||
//
|
||||
// The tombstone hash is derived as `hash(code_hash, storage_root_hash)`. In order to match
|
||||
// this hash to its own hash the restorer must make its storage equal to the one of the
|
||||
// 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.
|
||||
// The state rent functionality was removed. This is stub only exists for
|
||||
// backwards compatiblity
|
||||
[seal1] seal_restore_to(
|
||||
ctx,
|
||||
dest_ptr: u32,
|
||||
code_hash_ptr: u32,
|
||||
rent_allowance_ptr: u32,
|
||||
delta_ptr: u32,
|
||||
delta_count: u32
|
||||
_dest_ptr: u32,
|
||||
_code_hash_ptr: u32,
|
||||
_rent_allowance_ptr: u32,
|
||||
_delta_ptr: u32,
|
||||
_delta_count: u32
|
||||
) => {
|
||||
ctx.restore_to(
|
||||
dest_ptr,
|
||||
code_hash_ptr,
|
||||
rent_allowance_ptr,
|
||||
delta_ptr,
|
||||
delta_count,
|
||||
)
|
||||
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// 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(())
|
||||
},
|
||||
|
||||
// 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.
|
||||
// Was used to set rent allowance of the contract.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// The value `_VALUE_len` is ignored because the encoded sizes
|
||||
// this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards
|
||||
// compatibility. Consider switching to the newest version of this function.
|
||||
[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, _value_len: u32) => {
|
||||
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);
|
||||
// The state rent functionality was removed. This is stub only exists for
|
||||
// backwards compatiblity.
|
||||
[seal0] seal_set_rent_allowance(ctx, _value_ptr: u32, _value_len: u32) => {
|
||||
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
|
||||
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
|
||||
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||
[seal1] seal_set_rent_allowance(ctx, value_ptr: u32) => {
|
||||
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);
|
||||
// # Note
|
||||
//
|
||||
// The state rent functionality was removed. This is stub only exists for
|
||||
// backwards compatiblity.
|
||||
[seal1] seal_set_rent_allowance(ctx, _value_ptr: u32) => {
|
||||
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
|
||||
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`.
|
||||
// `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.
|
||||
// # Note
|
||||
//
|
||||
// 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) => {
|
||||
ctx.charge_gas(RuntimeCosts::RentAllowance)?;
|
||||
let rent_allowance = ctx.ext.rent_allowance().encode();
|
||||
ctx.charge_gas(RuntimeCosts::Balance)?;
|
||||
let rent_allowance = <BalanceOf<E::T>>::max_value().encode();
|
||||
Ok(ctx.write_sandbox_output(
|
||||
out_ptr, out_len_ptr, &rent_allowance, false, already_charged
|
||||
)?)
|
||||
@@ -1757,55 +1659,6 @@ define_env!(Env, <E: Ext>,
|
||||
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.
|
||||
//
|
||||
// This function decodes the passed in data as the overarching `Call` type of the
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user