contracts: Remove state rent (#9669)

* Remove storage rent

* Add storage migration

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

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

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

* Add migration for deletetion queue

* Fix compilation

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

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

Co-authored-by: Parity Bot <admin@parity.io>
This commit is contained in:
Alexander Theißen
2021-09-06 18:40:58 +02:00
committed by GitHub
parent 13f3e25ebb
commit 522e77e243
20 changed files with 1158 additions and 4641 deletions
File diff suppressed because it is too large Load Diff
+47 -423
View File
@@ -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()
});
+30 -197
View File
@@ -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.
+91 -5
View File
@@ -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
}
}
-577
View File
@@ -1,577 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A module responsible for computing the right amount of weight and charging it.
use crate::{
exec::Executable, gas::GasMeter, storage::Storage, wasm::PrefabWasmModule, AliveContractInfo,
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, Pallet,
TombstoneContractInfo,
};
use frame_support::{
storage::child,
traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons},
DefaultNoBound,
};
use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
use sp_core::crypto::UncheckedFrom;
use sp_io::hashing::blake2_256;
use sp_runtime::{
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
DispatchError,
};
use sp_std::prelude::*;
/// Information about the required deposit and resulting rent.
///
/// The easiest way to guarantee that a contract stays alive is to assert that
/// `max_rent == 0` at the **end** of a contract's execution.
///
/// # Note
///
/// The `current_*` fields do **not** consider changes to the code's refcount made during
/// the currently running call.
#[derive(codec::Encode, DefaultNoBound)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RentStatus<T: Config> {
/// Required deposit assuming that this contract is the only user of its code.
pub max_deposit: BalanceOf<T>,
/// Required deposit assuming the code's current refcount.
pub current_deposit: BalanceOf<T>,
/// Required deposit assuming the specified refcount (None if 0 is supplied).
pub custom_refcount_deposit: Option<BalanceOf<T>>,
/// Rent that is paid assuming that the contract is the only user of its code.
pub max_rent: BalanceOf<T>,
/// Rent that is paid given the code's current refcount.
pub current_rent: BalanceOf<T>,
/// Rent that is paid assuming the specified refcount (None is 0 is supplied).
pub custom_refcount_rent: Option<BalanceOf<T>>,
/// Reserved for backwards compatible changes to this data structure.
pub _reserved: Option<()>,
}
pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);
impl<T, E> Rent<T, E>
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
E: Executable<T>,
{
/// Make account paying the rent for the current block number
///
/// This functions does **not** evict the contract. It returns `None` in case the
/// contract is in need of eviction. [`try_eviction`] must
/// be called to perform the eviction.
pub fn charge(
account: &T::AccountId,
contract: AliveContractInfo<T>,
code_size: u32,
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
let current_block_number = <frame_system::Pallet<T>>::block_number();
let verdict =
Self::consider_case(account, current_block_number, Zero::zero(), &contract, code_size);
Self::enact_verdict(account, contract, current_block_number, verdict, None)
}
/// Process a report that a contract under the given address should be evicted.
///
/// Enact the eviction right away if the contract should be evicted and return the amount
/// of rent that the contract paid over its lifetime.
/// Otherwise, **do nothing** and return None.
///
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
/// of current block. E.g. if the contract is going to be evicted at the current block,
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain
/// snitchers relative to others.
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn try_eviction(
account: &T::AccountId,
handicap: T::BlockNumber,
) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {
let contract = <ContractInfoOf<T>>::get(account);
let contract = match contract {
None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),
Some(ContractInfo::Alive(contract)) => contract,
};
let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
let code_len = module.code_len();
let current_block_number = <frame_system::Pallet<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
handicap,
&contract,
module.occupied_storage(),
);
// Enact the verdict only if the contract gets removed.
match verdict {
Verdict::Evict { ref amount } => {
// The outstanding `amount` is withdrawn inside `enact_verdict`.
let rent_paid = amount
.as_ref()
.map(|a| a.peek())
.unwrap_or_else(|| <BalanceOf<T>>::zero())
.saturating_add(contract.rent_paid);
Self::enact_verdict(
account,
contract,
current_block_number,
verdict,
Some(module),
)?;
Ok((Some(rent_paid), code_len))
},
_ => Ok((None, code_len)),
}
}
/// Returns the projected time a given contract will be able to sustain paying its rent. The
/// returned projection is relevant for the current block, i.e. it is as if the contract was
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
/// evicted before or as a result of the rent collection.
///
/// The returned value is only an estimation. It doesn't take into account any top ups, changing
/// the rent allowance, or any problems coming from withdrawing the dues.
///
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
/// compute the projection. This function is only used for implementation of an RPC method
/// through `RuntimeApi` meaning that the changes will be discarded anyway.
pub fn compute_projection(account: &T::AccountId) -> RentProjectionResult<T::BlockNumber> {
use ContractAccessError::IsTombstone;
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let module = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)
.map_err(|_| IsTombstone)?;
let code_size = module.occupied_storage();
let current_block_number = <frame_system::Pallet<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
code_size,
);
// We skip the eviction in case one is in order.
// Evictions should only be performed by [`try_eviction`].
let new_contract_info =
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict, None);
// Check what happened after enaction of the verdict.
let alive_contract_info =
new_contract_info.map_err(|_| IsTombstone)?.ok_or_else(|| IsTombstone)?;
// Compute how much would the fee per block be with the *updated* balance.
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = Self::fee_per_block(&free_balance, &alive_contract_info, code_size);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction)
}
// Then compute how much the contract will sustain under these circumstances.
let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info)
.expect(
"the contract exists and in the alive state;
the updated balance must be greater than subsistence deposit;
this function doesn't return `None`;
qed
",
);
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
Some(blocks_left) => blocks_left,
None => {
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
// there is an overflow. This cannot happen with integers though. Return
// `NoEviction` here just in case.
return Ok(RentProjection::NoEviction)
},
};
let blocks_left = blocks_left.saturated_into::<u32>().into();
Ok(RentProjection::EvictionAt(current_block_number + blocks_left))
}
/// Restores the destination account using the origin as prototype.
///
/// The restoration will be performed iff:
/// - the supplied code_hash does still exist on-chain
/// - origin exists and is alive,
/// - the origin's storage is not written in the current block
/// - the restored account has tombstone
/// - the tombstone matches the hash of the origin storage root, and code hash.
///
/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
/// the restored account. The restored account will inherit the last write block and its last
/// deduct block will be set to the current block.
pub fn restore_to(
origin: &T::AccountId,
mut origin_contract: AliveContractInfo<T>,
dest: T::AccountId,
code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>,
delta: Vec<crate::exec::StorageKey>,
gas_meter: &mut GasMeter<T>,
) -> Result<(), DispatchError> {
let child_trie_info = origin_contract.child_trie_info();
let current_block = <frame_system::Pallet<T>>::block_number();
if origin_contract.last_write == Some(current_block) {
return Err(Error::<T>::InvalidContractOrigin.into())
}
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
.and_then(|c| c.get_tombstone())
.ok_or(Error::<T>::InvalidDestinationContract)?;
let last_write =
if !delta.is_empty() { Some(current_block) } else { origin_contract.last_write };
// Fails if the code hash does not exist on chain
E::add_user(code_hash, gas_meter)?;
// We are allowed to eagerly modify storage even though the function can
// fail later due to tombstones not matching. This is because the restoration
// is always called from a contract and therefore in a storage transaction.
// The failure of this function will lead to this transaction's rollback.
let bytes_taken: u32 = delta
.iter()
.filter_map(|key| {
let key = blake2_256(key);
child::get_raw(&child_trie_info, &key).map(|value| {
child::kill(&child_trie_info, &key);
value.len() as u32
})
})
.sum();
let tombstone = <TombstoneContractInfo<T>>::new(
// This operation is cheap enough because last_write (delta not included)
// is not this block as it has been checked earlier.
&child::root(&child_trie_info)[..],
code_hash,
);
if tombstone != dest_tombstone {
return Err(Error::<T>::InvalidTombstone.into())
}
origin_contract.storage_size -= bytes_taken;
<ContractInfoOf<T>>::remove(&origin);
E::remove_user(origin_contract.code_hash, gas_meter)?;
<ContractInfoOf<T>>::insert(
&dest,
ContractInfo::Alive(AliveContractInfo::<T> {
code_hash,
rent_allowance,
rent_paid: <BalanceOf<T>>::zero(),
deduct_block: current_block,
last_write,
..origin_contract
}),
);
let origin_free_balance = T::Currency::free_balance(&origin);
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
T::Currency::deposit_creating(&dest, origin_free_balance);
Ok(())
}
/// Create a new `RentStatus` struct for pass through to a requesting contract.
pub fn rent_status(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
aggregated_code_size: u32,
current_refcount: u32,
at_refcount: u32,
) -> RentStatus<T> {
let calc_share = |refcount: u32| aggregated_code_size.checked_div(refcount).unwrap_or(0);
let current_share = calc_share(current_refcount);
let custom_share = calc_share(at_refcount);
RentStatus {
max_deposit: Self::required_deposit(contract, aggregated_code_size),
current_deposit: Self::required_deposit(contract, current_share),
custom_refcount_deposit: if at_refcount > 0 {
Some(Self::required_deposit(contract, custom_share))
} else {
None
},
max_rent: Self::fee_per_block(free_balance, contract, aggregated_code_size),
current_rent: Self::fee_per_block(free_balance, contract, current_share),
custom_refcount_rent: if at_refcount > 0 {
Some(Self::fee_per_block(free_balance, contract, custom_share))
} else {
None
},
_reserved: None,
}
}
/// Returns how much deposit is required to not pay rent.
fn required_deposit(contract: &AliveContractInfo<T>, code_size_share: u32) -> BalanceOf<T> {
T::DepositPerStorageByte::get()
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
.saturating_add(
T::DepositPerStorageItem::get().saturating_mul(contract.pair_count.into()),
)
.saturating_add(T::DepositPerContract::get())
}
/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract
/// possesses enough funds then the fee can drop to zero.
fn fee_per_block(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
code_size_share: u32,
) -> BalanceOf<T> {
let missing_deposit =
Self::required_deposit(contract, code_size_share).saturating_sub(*free_balance);
T::RentFraction::get().mul_ceil(missing_deposit)
}
/// Returns amount of funds available to consume by rent mechanism.
///
/// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot
/// make the balance lower than [`subsistence_threshold`].
///
/// In case the toal_balance is below the subsistence threshold, this function returns `None`.
fn rent_budget(
total_balance: &BalanceOf<T>,
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = Pallet::<T>::subsistence_threshold();
// Reserved balance contributes towards the subsistence threshold to stay consistent
// with the existential deposit where the reserved balance is also counted.
if *total_balance < subsistence_threshold {
return None
}
// However, reserved balance cannot be charged so we need to use the free balance
// to calculate the actual budget (which can be 0).
let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);
Some(<BalanceOf<T>>::min(contract.rent_allowance, rent_allowed_to_charge))
}
/// Consider the case for rent payment of the given account and returns a `Verdict`.
///
/// Use `handicap` in case you want to change the reference block number. (To get more details
/// see `try_eviction` ).
fn consider_case(
account: &T::AccountId,
current_block_number: T::BlockNumber,
handicap: T::BlockNumber,
contract: &AliveContractInfo<T>,
code_size: u32,
) -> Verdict<T> {
// How much block has passed since the last deduction for the contract.
let blocks_passed = {
// Calculate an effective block number, i.e. after adjusting for handicap.
let effective_block_number = current_block_number.saturating_sub(handicap);
effective_block_number.saturating_sub(contract.deduct_block)
};
if blocks_passed.is_zero() {
// Rent has already been paid
return Verdict::Exempt
}
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
// An amount of funds to charge per block for storage taken up by the contract.
let fee_per_block = Self::fee_per_block(&free_balance, contract, code_size);
if fee_per_block.is_zero() {
// The rent deposit offset reduced the fee to 0. This means that the contract
// gets the rent for free.
return Verdict::Exempt
}
let rent_budget = match Self::rent_budget(&total_balance, &free_balance, contract) {
Some(rent_budget) => rent_budget,
None => {
// All functions that allow a contract to transfer balance enforce
// that the contract always stays above the subsistence threshold.
// We want the rent system to always leave a tombstone to prevent the
// accidental loss of a contract. Ony `seal_terminate` can remove a
// contract without a tombstone. Therefore this case should be never
// hit.
log::error!(
target: "runtime::contracts",
"Tombstoned a contract that is below the subsistence threshold: {:?}",
account,
);
0u32.into()
},
};
let dues = fee_per_block
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
.unwrap_or_else(|| <BalanceOf<T>>::max_value());
let insufficient_rent = rent_budget < dues;
// If the rent payment cannot be withdrawn due to locks on the account balance, then evict
// the account.
//
// NOTE: This seems problematic because it provides a way to tombstone an account while
// avoiding the last rent payment. In effect, someone could retroactively set rent_allowance
// for their contract to 0.
let dues_limited = dues.min(rent_budget);
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
account,
dues_limited,
WithdrawReasons::FEE,
free_balance.saturating_sub(dues_limited),
)
.is_ok();
if insufficient_rent || !can_withdraw_rent {
// The contract cannot afford the rent payment and has a balance above the subsistence
// threshold, so it leaves a tombstone.
let amount =
if can_withdraw_rent { Some(OutstandingAmount::new(dues_limited)) } else { None };
return Verdict::Evict { amount }
}
return Verdict::Charge {
// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
amount: OutstandingAmount::new(dues_limited),
}
}
/// Enacts the given verdict and returns the updated `ContractInfo`.
///
/// `alive_contract_info` should be from the same address as `account`.
///
/// # Note
///
/// if `evictable_code` is `None` an `Evict` verdict will not be enacted. This is for
/// when calling this function during a `call` where access to the soon to be evicted
/// contract should be denied but storage should be left unmodified.
fn enact_verdict(
account: &T::AccountId,
alive_contract_info: AliveContractInfo<T>,
current_block_number: T::BlockNumber,
verdict: Verdict<T>,
evictable_code: Option<PrefabWasmModule<T>>,
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
match (verdict, evictable_code) {
(Verdict::Evict { amount }, Some(code)) => {
// We need to remove the trie first because it is the only operation
// that can fail and this function is called without a storage
// transaction when called through `claim_surcharge`.
Storage::<T>::queue_trie_for_deletion(&alive_contract_info)?;
if let Some(amount) = amount {
amount.withdraw(account);
}
// Note: this operation is heavy.
let child_storage_root = child::root(&alive_contract_info.child_trie_info());
let tombstone = <TombstoneContractInfo<T>>::new(
&child_storage_root[..],
alive_contract_info.code_hash,
);
let tombstone_info = ContractInfo::Tombstone(tombstone);
<ContractInfoOf<T>>::insert(account, &tombstone_info);
code.drop_from_storage();
<Pallet<T>>::deposit_event(Event::Evicted(account.clone()));
Ok(None)
},
(Verdict::Evict { amount: _ }, None) => Ok(None),
(Verdict::Exempt, _) => {
let contract = ContractInfo::Alive(AliveContractInfo::<T> {
deduct_block: current_block_number,
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract);
Ok(Some(contract.get_alive().expect("We just constructed it as alive. qed")))
},
(Verdict::Charge { amount }, _) => {
let contract = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
rent_paid: alive_contract_info.rent_paid.saturating_add(amount.peek()),
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract);
amount.withdraw(account);
Ok(Some(contract.get_alive().expect("We just constructed it as alive. qed")))
},
}
}
}
/// The amount to charge.
///
/// This amount respects the contract's rent allowance and the subsistence deposit.
/// Because of that, charging the amount cannot remove the contract.
struct OutstandingAmount<T: Config> {
amount: BalanceOf<T>,
}
impl<T: Config> OutstandingAmount<T> {
/// Create the new outstanding amount.
///
/// The amount should be always withdrawable and it should not kill the account.
fn new(amount: BalanceOf<T>) -> Self {
Self { amount }
}
/// Returns the amount this instance wraps.
fn peek(&self) -> BalanceOf<T> {
self.amount
}
/// Withdraws the outstanding amount from the given account.
fn withdraw(self, account: &T::AccountId) {
if let Ok(imbalance) = T::Currency::withdraw(
account,
self.amount,
WithdrawReasons::FEE,
ExistenceRequirement::KeepAlive,
) {
// This should never fail. However, let's err on the safe side.
T::RentPayment::on_unbalanced(imbalance);
}
}
}
enum Verdict<T: Config> {
/// The contract is exempted from paying rent.
///
/// For example, it already paid its rent in the current block, or it has enough deposit for
/// not paying rent at all.
Exempt,
/// The contract cannot afford payment within its rent budget so it gets evicted. However,
/// because its balance is greater than the subsistence threshold it leaves a tombstone.
Evict { amount: Option<OutstandingAmount<T>> },
/// Everything is OK, we just only take some charge.
Charge { amount: OutstandingAmount<T> },
}
+3 -19
View File
@@ -267,11 +267,8 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_minimum_balance`.
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),
+32 -190
View File
@@ -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>,
+8 -125
View File
@@ -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#"
+65 -212
View File
@@ -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